﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Xml;
using MacomberMapSystem.Common.CIM;
using MacomberMapSystem.Common.Database;
using MacomberMapSystem.Common.Exports;
using MacomberMapSystem.Common.Serialization;
using MacomberMapSystem.Common.Integration;
using System.Runtime.InteropServices;
using MacomberMapSystem.Common.Internals;
using System.Drawing.Drawing2D;
using MacomberMapSystem.Common.User_Interfaces.CIM_Browser;
using System.Drawing.Imaging;
using System.Data.Common;
using System.IO;
using MacomberMapSystem.Common.Processing;
using System.Data.OracleClient;

namespace MacomberMapSystem.Common.User_Interfaces.One_Lines
{
    /// <summary>
    /// This class provides an editing environment for one-lines
    /// </summary>
    public partial class ctlOneLineEditor : UserControl
    {
        #region Variable declarations
        /// <summary>Our right-click menu</summary>
        private ContextMenuStrip RightClickMenu = new ContextMenuStrip();

        /// <summary>Our collection of elements on the one-line display</summary>
        public Dictionary<CIM_Element, MM_OneLine_Element> CIM_Elements = new Dictionary<CIM_Element, MM_OneLine_Element>();

        /// <summary>Our collection of descriptors</summary>
        public Dictionary<CIM_Element, MM_OneLine_Element> CIM_Descriptors = new Dictionary<CIM_Element, MM_OneLine_Element>();

        /// <summary>Our collection of secondary descriptors</summary>
        public Dictionary<CIM_Element, MM_OneLine_Element> CIM_SecondaryDescriptors = new Dictionary<CIM_Element, MM_OneLine_Element>();

        /// <summary>Our collectino of elements against their XML definitions</summary>
        public Dictionary<XmlElement, MM_OneLine_Element> Xml_Elements = new Dictionary<XmlElement, MM_OneLine_Element>();

        /// <summary>Our collection of XML tree nodes</summary>
        public Dictionary<MM_OneLine_Element, TreeNode> Xml_Nodes = new Dictionary<MM_OneLine_Element, TreeNode>();

        /// <summary>Our collection of CIM items</summary>
        private Dictionary<CIM_Element, CIMListViewItem> Xml_Items = new Dictionary<CIM_Element, CIMListViewItem>();

        /// <summary>The collection of Xml nodes</summary>
        private Dictionary<XmlElement, TreeNode> Xml_NodeList = new Dictionary<XmlElement, TreeNode>();

        /// <summary>Our collection of nodes</summary>
        public List<MM_OneLine_Element> Nodes = new List<MM_OneLine_Element>();

        /// <summary>The one-line database entry</summary>
        public MM_Database_OneLine OneLine;

        /// <summary>Our CIM model</summary>
        public MM_Database_Model CIM;

        /// <summary>The base element for the current one-line</summary>
        private CIM_Element _BaseElement;

        /// <summary>Our collection of RAS gates</summary>
        public List<CIM_Element> RASGates = new List<CIM_Element>();

        /// <summary>The element that is the container for one-line elements</summary>
        public CIM_Element BaseElement
        {
            get { return _BaseElement; }
            set
            {
                _BaseElement = value;
                if (OneLine != null && OneLine.Document != null && OneLine.Document.DocumentElement != null && value != null)
                {
                    OneLine.Document.DocumentElement.Attributes.Append(OneLine.Document.CreateAttribute("BaseElement.Name")).Value = value.Name;
                    OneLine.Document.DocumentElement.Attributes.Append(OneLine.Document.CreateAttribute("BaseElement.LongName")).Value = value.LongName;
                }
            }
        }

        /// <summary>The point where the mouse was pushed down</summary>
        private Point MouseDownPoint;

        /// <summary>The rectangle for handling multiple item selections</summary>
        private Rectangle DragRectangle;

        /// <summary>Handle clicking on the panel itself</summary>
        public event MouseEventHandler PanelMouseMove;

        /// <summary>Handle clicking on the panel itself</summary>
        public event MouseEventHandler PanelClicked;

        /// <summary>Handle clicking on the panel itself</summary>
        public event MouseEventHandler PanelDoubleClicked;

        /// <summary>The event that gets triggered every time our selection changes</summary>
        public event EventHandler SelectionChanged;

        /// <summary>The assignment associated with the edit</summary>
        public MM_Database_Assignment Assignment;

        /// <summary>Our last assignment</summary>
        public MM_Database_Assignment LastAssignment;

        /// <summary>The collection of selected elements</summary>
        public List<MM_OneLine_Element> SelectedElements
        {
            get
            {
                List<MM_OneLine_Element> OutElems = new List<MM_OneLine_Element>();
                foreach (MM_OneLine_Element Elem in pnlElements.Controls)
                    if (Elem.Selected)
                        OutElems.Add(Elem);
                return OutElems;
            }
        }

        /// <summary>The collection of selected elements and their descriptors</summary>
        public MM_OneLine_Element[] SelectedElementsAndTheirDescriptors
        {
            get
            {
                Dictionary<MM_OneLine_Element, bool> OutElems = new Dictionary<MM_OneLine_Element, bool>();
                foreach (MM_OneLine_Element Elem in pnlElements.Controls)
                    if (Elem.Selected)
                    {
                        if (!OutElems.ContainsKey(Elem))
                            OutElems.Add(Elem, true);
                        if (Elem.Descriptor != null && !OutElems.ContainsKey(Elem.Descriptor))
                            OutElems.Add(Elem.Descriptor, false);
                        if (Elem.SecondaryDescriptor != null && !OutElems.ContainsKey(Elem.SecondaryDescriptor))
                            OutElems.Add(Elem.SecondaryDescriptor, false);
                    }
                MM_OneLine_Element[] OutElemCollection = new MM_OneLine_Element[OutElems.Count];
                OutElems.Keys.CopyTo(OutElemCollection, 0);
                return OutElemCollection;
            }
        }


        /// <summary>The collection of selected elements that aren't descriptors</summary>
        public List<MM_OneLine_Element> SelectedNonDescriptorElements
        {
            get
            {
                List<MM_OneLine_Element> OutElems = new List<MM_OneLine_Element>();
                foreach (MM_OneLine_Element Elem in pnlElements.Controls)
                    if (Elem.Selected && Elem.ElemType != MM_OneLine_Element.enumElemTypes.Descriptor && Elem.ElemType != MM_OneLine_Element.enumElemTypes.SecondaryDescriptor)
                        OutElems.Add(Elem);
                return OutElems;
            }
        }

        /// <summary>Our event for the saving of a one-line</summary>
        public event EventHandler OneLineSaved;

        /// <summary>Our repository</summary>
        public MM_Repository Repository;
        #endregion

      

        #region Initialization
        /// <summary>
        /// Intialize a new one-line editor form
        /// </summary>
        public ctlOneLineEditor()
        {
            InitializeComponent();
            SelectionChanged += new EventHandler(ctlOneLineEditor_SelectionChanged);
            dgvAttribute.Columns.Add("Name", "Name");
            dgvAttribute.Columns.Add("Value", "Value");
            dgvAttribute.AllowUserToAddRows = false;
            dgvAttribute.AllowUserToDeleteRows = false;
            RightClickMenu.ItemClicked += new ToolStripItemClickedEventHandler(RightClickMenu_ItemClicked);
            //Double-buffer our pane
            pnlElements.GetType().GetProperty("DoubleBuffered", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).SetValue(pnlElements, true, null);
            lvElements.OwnerDraw = true;
            lvElements.DrawColumnHeader += new DrawListViewColumnHeaderEventHandler(lvElements_DrawColumnHeader);
            lvElements.DrawItem += new DrawListViewItemEventHandler(lvElements_DrawItem);
            lvElements.DrawSubItem += new DrawListViewSubItemEventHandler(lvElements_DrawSubItem);
        }

        
        /// <summary>
        /// Load in a new one-line
        /// </summary>
        /// <param name="OneLine"></param>
        /// <param name="BaseElement"></param>
        /// <param name="CIM"></param>
        /// <param name="Assignment"></param>
        /// <param name="Repository"></param>
        public void LoadOneLine(MM_Database_OneLine OneLine, MM_Database_Model CIM, CIM_Element BaseElement, MM_Database_Assignment Assignment, MM_Repository Repository)
        {
            //First, clone our one-line
            this.OneLine = new MM_Database_OneLine(OneLine);
            this.Assignment = Assignment;
            this.LastAssignment = Repository.Db.GetLastAssignment(OneLine);
            this.Repository = Repository;
            this.BaseElement = BaseElement;
            
            this.CIM = CIM;
            //Make sure we have our elements in place
            foreach (String ParentName in "Elements,Nodes,Unlinked_Elements".Split(','))
            if (this.OneLine.Document.DocumentElement[ParentName] == null)
                this.OneLine.Document.DocumentElement.AppendChild(this.OneLine.Document.CreateElement(ParentName));
            CIM_Elements.Clear();
            Xml_Elements.Clear();
            Xml_Nodes.Clear();
            Xml_Items.Clear();
            Nodes.Clear();
            CIM_Descriptors.Clear();
            CIM_SecondaryDescriptors.Clear();
            this.OneLine.Document.NodeInserted += new XmlNodeChangedEventHandler(Document_NodeEvent);
            this.OneLine.Document.NodeRemoved += new XmlNodeChangedEventHandler(Document_NodeEvent);
            this.OneLine.Document.NodeChanged += new XmlNodeChangedEventHandler(Document_NodeEvent);
            this.OneLine.PropigateMenu(this.btnInfo.DropDownItems);
            //Clear our old list of controls
            pnlElements.SuspendLayout();
            foreach (Control ctl in pnlElements.Controls)
                ctl.Dispose();
            pnlElements.Controls.Clear();
            pnlElements.AutoScrollPosition = Point.Empty;

            //Build a list of nodes that we need to remove because they don't apply to this model
            List<XmlElement> NodesToRemove = new List<XmlElement>();
            List<CIM_Element> ElementsWithinBase = new List<CIM_Element>(BaseElement.Elements);

            //Now, add in our list of elements, removing ones that don't make sense anymore
            MM_OneLine_Element NewElem;
            foreach (XmlElement ElemToAdd in this.OneLine.Document.SelectNodes("One_Line/Elements/*"))
                if ((NewElem = new MM_OneLine_Element(ElemToAdd, CIM, BaseElement)).CIM == null)
                {
                    NewElem.Dispose();
                    NodesToRemove.Add(ElemToAdd);
                }
                else if (NewElem.CIM.ElemType != ElemToAdd.Name)
                    if (MessageBox.Show("Note: An element discrepancy was found for " + NewElem.CIM.LineName + "/" + ElemToAdd.Attributes["BaseElement.Name"].Value + ". In the one-line, it's of type " + ElemToAdd.Name + ", while in CIM it's of type " + NewElem.CIM.ElemType + ".\nWould you like to change this element type?", Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                    {
                        //NewElem.ElemType = (MM_OneLine_Element.enumElemTypes)Enum.Parse(typeof(MM_OneLine_Element.enumElemTypes), NewElem.CIM.ElemType);
                        
                        //Create our new element right above, and move everything in.
                        MM_OneLine_Element.enumElemTypes ElemType = NewElem.ElemType;
                        XmlElement xNewElem = NewElem.Definition.OwnerDocument.CreateElement(ElemType.ToString());
                        while (NewElem.Definition.HasAttributes)
                            xNewElem.SetAttributeNode(NewElem.Definition.RemoveAttributeNode(NewElem.Definition.Attributes[0]));
                        while (NewElem.Definition.HasChildNodes)
                            xNewElem.AppendChild(NewElem.Definition.FirstChild);
                        NewElem.Definition.ParentNode.ReplaceChild(xNewElem, NewElem.Definition);
                        NewElem.Dispose();
                        NewElem = new MM_OneLine_Element(xNewElem, CIM, BaseElement);
                        NewElem.MouseDown += new MouseEventHandler(Elem_MouseDown);
                        NewElem.MouseMove += new MouseEventHandler(Elem_MouseMove);
                        NewElem.MouseUp += new MouseEventHandler(Elem_MouseUp);
                        NewElem.MouseDoubleClick += new MouseEventHandler(Elem_MouseDoubleClick);
                        NewElem.MouseDoubleClick += new MouseEventHandler(Elem_MouseDoubleClick);
                        //Recheck our size
                        NewElem.ChangeOrientation(NewElem.Orientation);
                        if (NewElem.CIM != null)
                        {
                            CIM_Elements.Add(NewElem.CIM, NewElem);
                            AddDescriptor(xNewElem["Descriptor"], CIM, NewElem);
                            AddDescriptor(xNewElem["SecondaryDescriptor"], CIM, NewElem);
                        }
                        Xml_Elements.Add(xNewElem, NewElem);
                        Rectangle TargetBounds = NewElem.Bounds;
                        this.pnlElements.Controls.Add(NewElem);

                        //Double-check our coordinates, because of shifting
                        if (NewElem.Bounds != TargetBounds)
                            Console.WriteLine("hmmm...");
                        if (!pnlElements.AutoScrollPosition.IsEmpty)
                            Console.WriteLine("hmmm...");
                    }
                    else
                    {
                        NewElem.Dispose();
                        NodesToRemove.Add(ElemToAdd);
                    }   
                //Check topology
                else if (!ElementsWithinBase.Contains(NewElem.CIM) || (NewElem.CIM != null && CIM_Elements.ContainsKey(NewElem.CIM)))
                {
                    NewElem.Dispose();
                    NodesToRemove.Add(ElemToAdd);
                }   
                else
                {
                    NewElem.MouseDown += new MouseEventHandler(Elem_MouseDown);
                    NewElem.MouseMove += new MouseEventHandler(Elem_MouseMove);
                    NewElem.MouseUp += new MouseEventHandler(Elem_MouseUp);
                    NewElem.MouseDoubleClick += new MouseEventHandler(Elem_MouseDoubleClick);
                    NewElem.MouseDoubleClick += new MouseEventHandler(Elem_MouseDoubleClick);
                    if (NewElem.CIM != null)
                    {
                        CIM_Elements.Add(NewElem.CIM, NewElem);
                        AddDescriptor(ElemToAdd["Descriptor"], CIM, NewElem);
                        AddDescriptor(ElemToAdd["SecondaryDescriptor"], CIM, NewElem);
                    }
                    Xml_Elements.Add(ElemToAdd, NewElem);
                    this.pnlElements.Controls.Add(NewElem);
                }

            //Do the same for nodes
            foreach (XmlElement ElemToAdd in this.OneLine.Document.SelectNodes("One_Line/Nodes/*"))
                if ((NewElem = new MM_OneLine_Element(ElemToAdd, CIM, BaseElement)).CIM == null || NewElem.CIM.ElemType != ElemToAdd.Name || !ElementsWithinBase.Contains(NewElem.CIM) || (NewElem.CIM != null && CIM_Elements.ContainsKey(NewElem.CIM)))
                {
                    NewElem.Dispose();
                    NodesToRemove.Add(ElemToAdd);
                }
                else
                {
                    NewElem.MouseDown += new MouseEventHandler(Elem_MouseDown);
                    NewElem.MouseMove += new MouseEventHandler(Elem_MouseMove);
                    NewElem.MouseUp += new MouseEventHandler(Elem_MouseUp);
                    NewElem.MouseDoubleClick += new MouseEventHandler(Elem_MouseDoubleClick);
                    if (NewElem.CIM != null)
                        CIM_Elements.Add(NewElem.CIM, NewElem);
                    Xml_Elements.Add(ElemToAdd, NewElem);
                    this.pnlElements.Controls.Add(NewElem);

                    //Now, build our collection of target elements
                    XmlElement NodeTarget;
                    MM_OneLine_Element TargetElem;
                    Dictionary<MM_OneLine_Element, MM_OneLine_Element[]> TargetElems = new Dictionary<MM_OneLine_Element, MM_OneLine_Element[]>();
                    foreach (XmlElement xNodeTarget in ElemToAdd.ChildNodes)
                        if (xNodeTarget.Name == "Descriptor")
                            if (NewElem.CIM.ParseSingle("cim:Terminal>cim:BusbarSection", null) == null)
                                NodesToRemove.Add(xNodeTarget);
                            else
                                AddDescriptor(xNodeTarget, CIM, NewElem);                        
                        else if (xNodeTarget.Name == "SecondaryDescriptor")
                            if (NewElem.CIM.ParseSingle("etx:ElectricalBus<etx:ResourceNode", null) == null)
                                NodesToRemove.Add(xNodeTarget);
                            else
                                AddDescriptor(xNodeTarget, CIM, NewElem);                                                                                          
                        else if ((NodeTarget = xNodeTarget.OwnerDocument.SelectSingleNode("//*[@BaseElement.TEID='" + xNodeTarget.Attributes["TEID"].Value + "']") as XmlElement) == null || !Xml_Elements.TryGetValue(NodeTarget, out TargetElem))
                            NodesToRemove.Add(xNodeTarget);
                        else if (TargetElem.CIM.TypeName == "cim:PowerTransformer" &&Array.IndexOf(NewElem.CIM.Parse("Terminal>TransformerWinding>PowerTransformer",null), TargetElem.CIM) == -1 )
                            NodesToRemove.Add(xNodeTarget);

                        else if (TargetElem.CIM.TypeName != "cim:PowerTransformer" && Array.IndexOf(NewElem.CIM.Elements, TargetElem.CIM) == -1)
                            NodesToRemove.Add(xNodeTarget);
                        else
                        {
                            //Now, build our list of all points along the way                                                
                            foreach (XmlElement ChildElem in xNodeTarget.ChildNodes)
                                if (ChildElem.Name == "PricingVector")
                                {
                                    MM_OneLine_Element NewPV = new MM_OneLine_Element(ChildElem, CIM, BaseElement);
                                    if (NewPV.CIM == null || NewPV.CIM.ElemType != ChildElem.Name || CIM_Elements.ContainsKey(NewPV.CIM))
                                    {
                                        NodesToRemove.Add(ChildElem);
                                        NewPV.Dispose();
                                    }
                                    else
                                    {
                                        NewPV.MouseDown += new MouseEventHandler(Elem_MouseDown);
                                        NewPV.MouseMove += new MouseEventHandler(Elem_MouseMove);
                                        NewPV.MouseUp += new MouseEventHandler(Elem_MouseUp);
                                        NewPV.MouseDoubleClick += new MouseEventHandler(Elem_MouseDoubleClick);
                                        Xml_Elements.Add(ChildElem, NewPV);
                                        CIM_Elements.Add(NewPV.CIM, NewPV);
                                        pnlElements.Controls.Add(NewPV);
                                        AddDescriptor(ChildElem["Descriptor"], CIM, NewPV);
                                        AddDescriptor(ChildElem["SecondaryDescriptor"], CIM, NewPV);
                                    }
                                }
                                else if (ChildElem.Name == "PokePoint")
                                {
                                        
                                    MM_OneLine_Element NewPoke = new MM_OneLine_Element(ChildElem, CIM, BaseElement);
                                    NewPoke.MouseDown += new MouseEventHandler(Elem_MouseDown);
                                    NewPoke.MouseMove += new MouseEventHandler(Elem_MouseMove);
                                    NewPoke.MouseUp += new MouseEventHandler(Elem_MouseUp);
                                    NewPoke.MouseDoubleClick += new MouseEventHandler(Elem_MouseDoubleClick);
                                    Xml_Elements.Add(ChildElem, NewPoke);
                                    pnlElements.Controls.Add(NewPoke);
                                    AddDescriptor(ChildElem["Descriptor"], CIM, NewElem);
                                    AddDescriptor(ChildElem["SecondaryDescriptor"], CIM, NewElem);
                                }
                            NewElem.RebuildNodePath(TargetElem, xNodeTarget);
                        }

                 
                    
                    Nodes.Add(NewElem);
                }

            //Add in our unlinked elements
            foreach (XmlElement ElemToAdd in this.OneLine.Document.SelectNodes("One_Line/Unlinked_Elements/*"))
                if (ElemToAdd.Name == "Unlinked_Element")
                    NodesToRemove.Add(ElemToAdd);
                else
                {
                    MM_OneLine_Element NewUnlinked = new MM_OneLine_Element(ElemToAdd, CIM, BaseElement);
                    NewUnlinked.MouseDown += new MouseEventHandler(Elem_MouseDown);
                    NewUnlinked.MouseMove += new MouseEventHandler(Elem_MouseMove);
                    NewUnlinked.MouseUp += new MouseEventHandler(Elem_MouseUp);
                    NewUnlinked.MouseDoubleClick += new MouseEventHandler(Elem_MouseDoubleClick);
                    Xml_Elements.Add(ElemToAdd, NewUnlinked);
                    pnlElements.Controls.Add(NewUnlinked);
                }

            //Build our list of RAS gates
            CIM_Element OutputGate;
            RASGates.Clear();
            foreach (CIM_Element Elem in BaseElement.Elements)
                foreach (CIM_Element SPS in Elem.SPSorRAPs)
                    foreach (CIM_Element Gate in SPS.Parse("TriggerCondition<Gate", null))
                        try
                        {
                            String ShowTelem = Gate.ParseSingle("TriggerCondition>RemedialActionScheme", CIM)["etx:RemedialActionScheme.showTelemOnDisplay"];
                            if (!RASGates.Contains(Gate) && (String.IsNullOrEmpty(ShowTelem) || XmlConvert.ToBoolean(ShowTelem)))
                                if (Gate.ParseSingle("HasAMeasurement", null) != null)
                                    RASGates.Add(Gate);
                                else if ((OutputGate = Gate.ParseSingle("TriggerCondition>Gate", null)) != null && !RASGates.Contains(OutputGate) && Gate.ParseSingle("TriggerCondition<Gate<HasAMeasurement", null) == null)
                                    RASGates.Add(OutputGate);
                        }
                        catch (Exception ex)
                        { }


            //Remove all unwanted components from the model
            StringBuilder sWarning = new StringBuilder();
            sWarning.AppendLine("The following elements were not found in this model, and removed from the one-line:");
            foreach (XmlElement xRemove in NodesToRemove)
                if (xRemove.HasAttribute("TEID"))
                {
                    XmlElement xTargetElement = OneLine.Document.SelectSingleNode("//*[@BaseElement.TEID='" + xRemove.Attributes["TEID"].Value + "']") as XmlElement;
                    if (xTargetElement.HasAttribute("BaseElement.SubLongName"))
                        sWarning.AppendLine("Linkage from " + xRemove.ParentNode.Name + " " + xRemove.ParentNode.Attributes["BaseElement.Name"].Value + " to " + xRemove.Name + " " + xTargetElement.Attributes["BaseElement.Name"].Value + " in " + xTargetElement.Attributes["BaseElement.SubLongName"].Value);
                    else if (xTargetElement.HasAttribute("BaseElement.SubName"))
                        sWarning.AppendLine("Linkage from " + xRemove.ParentNode.Name + " " + xRemove.ParentNode.Attributes["BaseElement.Name"].Value + " to " + xRemove.Name + " " + xTargetElement.Attributes["BaseElement.Name"].Value + " in " + xTargetElement.Attributes["BaseElement.SubName"].Value);
                    else
                        sWarning.AppendLine("Linkage from " + xRemove.ParentNode.Name + " " + xRemove.ParentNode.Attributes["BaseElement.Name"].Value + " to " + xRemove.Name + " " + xTargetElement.Attributes["BaseElement.Name"].Value);
                }
                else if (xRemove.HasAttribute("BaseElement.SubLongName"))
                    sWarning.AppendLine(xRemove.Name + " " + xRemove.Attributes["BaseElement.Name"].Value + " in " + xRemove.Attributes["BaseElement.SubLongName"].Value);
                else if (xRemove.HasAttribute("BaseElement.SubName"))
                    sWarning.AppendLine(xRemove.Name + " " + xRemove.Attributes["BaseElement.Name"].Value + " in " + xRemove.Attributes["BaseElement.SubName"].Value);
                else if (xRemove.HasAttribute("BaseElement.Name"))
                    sWarning.AppendLine(xRemove.Name + " " + xRemove.Attributes["BaseElement.Name"].Value);
                else
                    sWarning.AppendLine(xRemove.Name);

            foreach (XmlElement xRemove in NodesToRemove)             
                xRemove.ParentNode.RemoveChild(xRemove);                


            pnlElements.ResumeLayout();
            pnlElements.AutoScroll = true;
            

            //Now, add in our elements to our list
            lvElements.Items.Clear();
            SortedDictionary<String, SortedDictionary<String, CIM_Element>> ElemsToAdd = new SortedDictionary<string, SortedDictionary<string, CIM_Element>>();
            SortedDictionary<String, CIM_Element> Grouping;
            String[] AcceptedElements = "Node,SynchronousMachine,Load,Breaker,cim:Disconnector,Compensator,LineSegment,EndCap,PricingVector,Transformer".Split(',');
            foreach (CIM_Element Elem in BaseElement.Elements)
                foreach (String Acc in AcceptedElements)
                    if (Elem.TypeName.EndsWith(Acc))                       
                        {
                            if (!ElemsToAdd.TryGetValue(Elem.TypeName.Split(':')[1], out Grouping))
                                ElemsToAdd.Add(Elem.TypeName.Split(':')[1], Grouping = new SortedDictionary<string, CIM_Element>());
                            String TargetName = MM_Type_Converter.TitleCase(MM_OneLine_Element.DescriptorText(Elem, BaseElement, Elem.TypeName.EndsWith("ACLineSegment") || Elem.TypeName.EndsWith("SeriesCompensator") || Elem.TypeName.EndsWith("PricingVector")));
                            if (Grouping.ContainsKey(TargetName))
                            {
                                int a = 2;
                                while (Grouping.ContainsKey(TargetName + " #" + a.ToString()))
                                    a++;
                                MessageBox.Show("Warning, there is more than one " + Elem.TypeName.Split(':')[1] + " named " + TargetName + " in the one-line.", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Warning);
                                TargetName += " #" + a.ToString();
                            }
                            Grouping.Add(TargetName, Elem);
                        }
            //Now, add in our sorted elements
            lvElements.BeginUpdate();
            foreach (KeyValuePair<String, SortedDictionary<String, CIM_Element>> kvp in ElemsToAdd)
            {
                ListViewGroup lvG = lvElements.Groups.Add(kvp.Key, kvp.Key);
                foreach (KeyValuePair<String, CIM_Element> kvp2 in kvp.Value)
                {
                    CIMListViewItem NewItem = new CIMListViewItem(kvp2.Key, kvp2.Value, lvG, CIM_Elements, CIM_Descriptors, CIM_SecondaryDescriptors);
                    lvElements.Items.Add(NewItem);
                    Xml_Items.Add(kvp2.Value, NewItem);                    
                }
            }
            lvElements.EndUpdate();

            //Now, refresh all the elements
            lvElements.BeginUpdate();
            foreach (CIMListViewItem lvI in lvElements.Items)
                lvI.Refresh(CIM_Elements, CIM_Descriptors, CIM_SecondaryDescriptors);
            lvElements.EndUpdate();

            
            lvElements.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);


            //Update our Xml
            tvXml.BeginUpdate();
            tvXml.Nodes.Clear();            
            WriteNode(this.OneLine.Document.DocumentElement, tvXml.Nodes);
            tvXml.EndUpdate();
            pnlElements.Refresh();

            if (NodesToRemove.Count > 0)
                MessageBox.Show(sWarning.ToString(), Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
            
        }

        /// <summary>
        /// Update our combo box with information from our validation results.
        /// </summary>
        /// <param name="ValidationResults"></param>
        public void ApplyValidationResults(String ValidationResults)
        {
            //Start by clearing the existing results, and build our dictionary of elements
            Dictionary<String, CIMListViewItem> Elements = new Dictionary<string, CIMListViewItem>(StringComparer.CurrentCultureIgnoreCase);
            foreach (CIMListViewItem clv in lvElements.Items)
            {
                clv.ImageKey = "";
                if (clv.AssociatedElement.TypeName == "cim:ACLineSegment" || clv.AssociatedElement.TypeName == "cim:SeriesCompensator")
                {
                    CIM_Element ParentLine = clv.AssociatedElement.ParseSingle("cim:Line", null);
                    Elements.Add(clv.AssociatedElement.ElemType + " " + ParentLine.Name.ToUpper() + (ParentLine.InLinks.Length == 1 ? "" : clv.AssociatedElement.Name.ToUpper()), clv);
                }
                else if (Elements.ContainsKey(clv.AssociatedElement.ElemType + " " + clv.AssociatedElement.Name))
                {
                    int a = 2;
                    while (Elements.ContainsKey(clv.AssociatedElement.ElemType + " " + clv.AssociatedElement.Name + " #" + a.ToString()))
                        a++;
                    Elements.Add(clv.AssociatedElement.ElemType + " " + clv.AssociatedElement.Name + " #" + a.ToString(), clv);
                }
                else
                    Elements.Add(clv.AssociatedElement.ElemType + " " + clv.AssociatedElement.Name, clv);
            }

            //Build our list of elements we'll search for
            
            


            //Now, read the results line-by-line
            String InLine;
            using (System.IO.StringReader sRd = new System.IO.StringReader(ValidationResults))
                while ((InLine = sRd.ReadLine()) != null)
                    if (InLine.StartsWith("\tWRN") || InLine.StartsWith("\tERR") || InLine.StartsWith("\tHTW") || InLine.StartsWith("WRN") || InLine.StartsWith("ERR") || InLine.StartsWith("HTW"))
                        foreach (String str in InLine.Split(new string[] {" with ", " for "}, StringSplitOptions.RemoveEmptyEntries))
                    {
                        //Grab the next two words after the colon:
                        String Elem = str.Substring(str.IndexOf(':') + 1).TrimStart();                                                                                                     
                        int Spc = Elem.IndexOf(' ');
                        Spc = Elem.IndexOf(' ', Spc+1);
                        if (Spc != -1)
                        {
                            Elem = Elem.Substring(0, Spc);
                            CIMListViewItem FoundItem = null;
                            if (Elements.TryGetValue(Elem, out FoundItem))
                            {
                                if (InLine.StartsWith("\tERR") || InLine.StartsWith("ERR"))
                                    FoundItem.ImageKey = "Bad";
                                else if ((InLine.StartsWith("\tHTW") || InLine.StartsWith("HTW")) && FoundItem.ImageKey != "Bad")
                                    FoundItem.ImageKey = "Alert";
                                else if ((InLine.StartsWith("\tWRN") || InLine.StartsWith("WRN")) && FoundItem.ImageKey != "Bad" && FoundItem.ImageKey != "Alert")
                                    FoundItem.ImageKey = "ActionRequired";
                            }                            
                        }
                    }
        }


        /// <summary>
        /// Handle a node event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Document_NodeEvent(object sender, XmlNodeChangedEventArgs e)
        {
            TreeNode ActionNode;
            if (e.Action == XmlNodeChangedAction.Change && e.NewParent is XmlAttribute && Xml_NodeList.TryGetValue((e.NewParent as XmlAttribute).OwnerElement, out ActionNode))
            {
                if (tvXml.SelectedNode == ActionNode && tcMain.SelectedTab == tpXml)
                    tvXml_AfterSelect(tvXml, new TreeViewEventArgs(ActionNode));
            }
            else if (e.Action == XmlNodeChangedAction.Insert && e.Node is XmlElement && e.NewParent != null && e.NewParent.ParentNode != null && Xml_NodeList.TryGetValue(e.NewParent as XmlElement, out ActionNode))
                WriteNode(e.Node as XmlElement, ActionNode.Nodes);            
            else if (e.Action == XmlNodeChangedAction.Remove && e.Node is XmlElement && e.OldParent != null && Xml_NodeList.TryGetValue(e.Node as XmlElement, out ActionNode))
            {
                ActionNode.Remove();
                Xml_NodeList.Remove(e.Node as XmlElement);
            }
               
        }

        /// <summary>
        /// Write out a node to the collection of nodes
        /// </summary>
        /// <param name="ElemToWrite"></param>
        /// <param name="tnColl"></param>
        private void WriteNode(XmlElement ElemToWrite, TreeNodeCollection tnColl)
        {
            TreeNode NewNode = tnColl.Add(ElemToWrite.Name + (ElemToWrite.HasAttribute("BaseElement.Name") ? " " + ElemToWrite.Attributes["BaseElement.Name"].Value : ""));
            NewNode.Tag = ElemToWrite;
            Xml_NodeList.Add(ElemToWrite, NewNode);
            MM_OneLine_Element Elem;
            if (Xml_Elements.TryGetValue(ElemToWrite, out Elem))
                Xml_Nodes.Add(Elem, NewNode);
            foreach (XmlElement xChild in ElemToWrite.ChildNodes)
                WriteNode(xChild, NewNode.Nodes);
        }


        /// <summary>
        /// Add in a new descriptor or secondary descriptor
        /// </summary>
        /// <param name="xElem">The element to add</param>
        /// <param name="CIM">The network model</param>
        /// <param name="ParentElement">The parent element for our item</param>
        private void AddDescriptor(XmlElement xElem, MM_Database_Model CIM, MM_OneLine_Element ParentElement)
        {
            if (xElem == null)
                return;
            MM_OneLine_Element Desc = new MM_OneLine_Element(xElem, CIM, BaseElement);


            Xml_Elements.Add(xElem, Desc);
            pnlElements.Controls.Add(Desc);
          
            Desc.ParentElement = ParentElement;
            Desc.ExportValues = MM_OneLine_Element.BuildExportPointsWithLocations(ParentElement.Definition, MM_OneLine_Element.BuildExportPoints(Desc.CIM, BaseElement, Desc.ElemType == MM_OneLine_Element.enumElemTypes.SecondaryDescriptor,xElem), Desc.ParentElement.Bounds, Desc.Bounds);

          
            Desc.MouseDown += new MouseEventHandler(Elem_MouseDown);
            Desc.MouseMove += new MouseEventHandler(Elem_MouseMove);
            Desc.MouseUp += new MouseEventHandler(Elem_MouseUp);
            Desc.MouseDoubleClick += new MouseEventHandler(Elem_MouseDoubleClick);
            if (xElem.Name == "Descriptor")
            {
                if (Desc.CIM != null)
                    CIM_Descriptors.Add(Desc.CIM, Desc);
                ParentElement.Descriptor = Desc;
            }
            else
            {
                if (Desc.CIM != null)
                    CIM_SecondaryDescriptors.Add(Desc.CIM, Desc);
                ParentElement.SecondaryDescriptor = Desc;
            }
        }
        #endregion

        #region Rendering
        /// <summary>
        /// Handle painting of the display
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void pnlElements_Paint(object sender, PaintEventArgs e)
        {

            if (BaseElement == null)
                return;
            //Draw our header bar
            Rectangle HeaderRect = new Rectangle(pnlElements.DisplayRectangle.Left, pnlElements.DisplayRectangle.Top, pnlElements.DisplayRectangle.Width, 40);
            if (HeaderRect.IntersectsWith(e.ClipRectangle))
                MM_OneLine_Element.DrawHeader(e.Graphics, HeaderRect, OneLine, CIM, LastAssignment);


            //Draw our shading background
            if (!noneToolStripMenuItem.Checked)
            {
                HatchBrush hB;
                if (bothToolStripMenuItem.Checked)
                    hB = new HatchBrush(HatchStyle.LightHorizontal, Color.Purple, Color.DarkBlue);
                else if (netmomToolStripMenuItem.Checked)
                    hB = new HatchBrush(HatchStyle.LightDownwardDiagonal, Color.Purple);
                else
                    hB = new HatchBrush(HatchStyle.LightVertical, Color.MediumBlue);

                foreach (MM_OneLine_Element Elem in pnlElements.Controls)
                    if (Elem.ExportValues == null && e.ClipRectangle.IntersectsWith(Elem.Bounds))
                        e.Graphics.FillRectangle(hB, Elem.Left - 1, Elem.Top - 1, Elem.Width + 2, Elem.Height + 2);
                    else if (Elem.ExportValues != null)
                    {
                        foreach (MM_OneLine_Value ExportValue in Elem.ExportValues)
                        {
                            Rectangle Rect = ExportValue.Bounds;
                            Rect.X += pnlElements.AutoScrollPosition.X;
                            Rect.Y += pnlElements.AutoScrollPosition.Y;
                            if (e.ClipRectangle.IntersectsWith(Rect))
                                if (bothToolStripMenuItem.Checked || (sCADAToolStripMenuItem.Checked && ExportValue.OutputMode == MM_OneLine_Value.enumOutputMode.Scada) || (netmomToolStripMenuItem.Checked && ExportValue.OutputMode == MM_OneLine_Value.enumOutputMode.Netmom))
                                {
                                    e.Graphics.FillRectangle(hB, Rect);
                                    e.Graphics.DrawString(ExportValue.ValueType.ToString(), this.Font, Brushes.Green, Rect.Location);
                                }
                        }
                        
                    }

                //If we have a RAS display, show here too
                if (RASGates.Count > 0)
                    e.Graphics.FillRectangle(hB, 0 + pnlElements.AutoScrollPosition.X, 48 + pnlElements.AutoScrollPosition.Y, 160, (25 * RASGates.Count) + 17);

                hB.Dispose();
            }

            //Draw our arrows
            foreach (MM_OneLine_Element Elem in pnlElements.Controls)
                if (Elem.DescriptorArrow)
                {
                    if (Elem.ParentElement != null)
                        MM_OneLine_Element.DrawArrow(e.Graphics, Elem.ParentElement.Bounds, Elem.Bounds);
                    if (Elem.Descriptor != null)
                        MM_OneLine_Element.DrawArrow(e.Graphics, Elem.Bounds, Elem.Descriptor.Bounds);
                    if (Elem.SecondaryDescriptor != null)
                        MM_OneLine_Element.DrawArrow(e.Graphics, Elem.Bounds, Elem.SecondaryDescriptor.Bounds);
                }

            if (!DragRectangle.IsEmpty)
            {
                e.Graphics.DrawLine(Pens.White, DragRectangle.Left, DragRectangle.Top, DragRectangle.Right, DragRectangle.Top);
                e.Graphics.DrawLine(Pens.White, DragRectangle.Right, DragRectangle.Top, DragRectangle.Right, DragRectangle.Bottom);
                e.Graphics.DrawLine(Pens.White, DragRectangle.Right, DragRectangle.Bottom, DragRectangle.Left, DragRectangle.Bottom);
                e.Graphics.DrawLine(Pens.White, DragRectangle.Left, DragRectangle.Bottom, DragRectangle.Left, DragRectangle.Top);


            }
            else
                //Draw our highlighting boxes
                foreach (MM_OneLine_Element Elem in SelectedElements)
                {
                    Rectangle Rect = Elem.Bounds;
                    if (Elem.Descriptor != null)
                        Rect = UnionRectangle(Rect, Elem.Descriptor.Bounds);
                    if (Elem.SecondaryDescriptor != null)
                        Rect = UnionRectangle(Rect, Elem.SecondaryDescriptor.Bounds);
                    if (e.ClipRectangle.IntersectsWith(Rect))
                        e.Graphics.DrawRectangle(Pens.White, Rect.Left - 2, Rect.Top - 2, Rect.Width + 4, Rect.Height + 4);
                }
            //Write out our nodes            
            e.Graphics.TranslateTransform(pnlElements.AutoScrollPosition.X, pnlElements.AutoScrollPosition.Y);
            foreach (MM_OneLine_Element Node in Nodes)
                if (Node.NodePaths != null)
                    foreach (GraphicsPath gpNode in Node.NodePaths.Values)
                        e.Graphics.DrawPath(Node.KVLevel.Energized.ForePen, gpNode);
            //Write out our resolution guidelines
            if (btnShowResolutionGuide.Checked)
                using (Pen ResolutionPen = new Pen(Color.Red, 1f))
                {
                    ResolutionPen.DashStyle = DashStyle.Dash;
                    for (int x = 0; x <= pnlElements.DisplayRectangle.Width; x += 1255)
                        for (int y = 0; y <= pnlElements.DisplayRectangle.Height; y += 838)
                        {
                            if (y > 0)
                                e.Graphics.DrawLine(ResolutionPen, x, y, x + 1255, y);
                            if (x > 0)
                                e.Graphics.DrawLine(ResolutionPen, x, y, x, y + 838);
                        }
                }
            e.Graphics.TranslateTransform(-pnlElements.AutoScrollPosition.X, -pnlElements.AutoScrollPosition.Y);
        }



        #endregion

        #region Panel mouse handling
        /// <summary>
        /// Handle the mouse being pressed on the panel
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void pnlElements_MouseDown(object sender, MouseEventArgs e)
        {

        }

        /// <summary>
        /// Handle a click in the element panel
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void pnlElements_MouseClick(object sender, MouseEventArgs e)
        {
            if (PanelClicked != null)
                PanelClicked(sender, new MouseEventArgs(e.Button, e.Clicks, e.X - pnlElements.AutoScrollPosition.X, e.Y - pnlElements.AutoScrollPosition.Y, e.Delta));
            if ((Control.ModifierKeys & Keys.Shift) != Keys.Shift)
            {
                DragRectangle = Rectangle.Empty;
                List<MM_OneLine_Element> Elems = SelectedElements;
                foreach (MM_OneLine_Element Elem in Elems)
                    Elem.Selected = false;
                RefreshElements(Elems.ToArray());
                SelectionChanged(sender, e);
                pnlElements.Refresh();
            }
        }


        /// <summary>
        /// Handle the mouse being moved on the panel
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void pnlElements_MouseMove(object sender, MouseEventArgs e)
        {
            if (PanelMouseMove != null)
                PanelMouseMove(sender, new MouseEventArgs(e.Button, e.Clicks, e.X - pnlElements.AutoScrollPosition.X, e.Y - pnlElements.AutoScrollPosition.Y, e.Delta));
            if (e.Button == MouseButtons.Left && (Control.ModifierKeys & Keys.Shift) == Keys.Shift)
                if (DragRectangle.IsEmpty)
                    DragRectangle = new Rectangle(e.Location, Size.Empty);
                else
                {                    
                    Rectangle Rect = OptimizeRectangle(DragRectangle);
                    DragRectangle.Width = e.Location.X - DragRectangle.Left;
                    DragRectangle.Height = e.Location.Y - DragRectangle.Top;
                    Rect = UnionRectangle(Rect, DragRectangle);
                    InvalidatePanel(Rect);                                        
                }
        }

        /// <summary>
        /// Optmize a rectangle, enuring negative widths and heights are translated propery
        /// </summary>
        /// <param name="InRect"></param>
        /// <returns></returns>
        private Rectangle OptimizeRectangle(Rectangle InRect)
        {
            if (InRect.Width < 0)
            {
                InRect.Width *= -1;
                InRect.X -= InRect.Width;
            }
            if (InRect.Height < 0)
            {
                InRect.Height *= -1;
                InRect.Y -= InRect.Height;
            }
            return InRect;
        }

        /// <summary>
        /// Invalid the panel around a rectangle
        /// </summary>
        /// <param name="Rect"></param>
        private void InvalidatePanel(Rectangle Rect)
        {
            pnlElements.Invalidate(Rectangle.FromLTRB(Rect.Left - 2, Rect.Top - 2, Rect.Right + 4, Rect.Bottom + 4));                    
        }

        /// <summary>
        /// Handle the mouse up on the panel
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void pnlElements_MouseUp(object sender, MouseEventArgs e)
        {
            //Set the update flag to true
            pnlElements.Tag = true;

            if (e.Button == MouseButtons.Left)
                if (!DragRectangle.IsEmpty)
                {
                    DragRectangle = Rectangle.FromLTRB(Math.Min(DragRectangle.Left, DragRectangle.Right), Math.Min(DragRectangle.Top, DragRectangle.Bottom), Math.Max(DragRectangle.Left, DragRectangle.Right), Math.Max(DragRectangle.Top, DragRectangle.Bottom));
                    foreach (MM_OneLine_Element Elem in pnlElements.Controls)
                    {
                        if (Elem.ElemType == MM_OneLine_Element.enumElemTypes.PokePoint || Elem.ElemType == MM_OneLine_Element.enumElemTypes.Node)
                            Elem.Selected = DragRectangle.Contains(Elem.Bounds);
                        else
                            Elem.Selected = DragRectangle.IntersectsWith(Elem.Bounds);

                        CIMListViewItem lvI;
                        if (Elem.CIM != null && Xml_Items.TryGetValue(Elem.CIM, out lvI))
                            lvI.Selected = Elem.Selected;
                    }

                    DragRectangle = Rectangle.Empty;
                    RefreshElements();
                    pnlElements.Refresh();
                    SelectionChanged(sender, e);
                }
                else
                {
                    List<MM_OneLine_Element> Elems = SelectedElements;
                    foreach (MM_OneLine_Element Elem in Elems)
                        Elem.Selected = false;
                    foreach (CIMListViewItem lvI in lvElements.Items)
                        lvI.Selected = false;
                    RefreshElements(Elems.ToArray());
                }
            else if (e.Button == MouseButtons.Right)
            {
                //If we right-clicked, look to find poke-points that could be created
                Dictionary<MM_OneLine_Element, MM_OneLine_Element> Pokes = new Dictionary<MM_OneLine_Element, MM_OneLine_Element>();
                RightClickMenu.Items.Clear();
                using (Pen HitPen = new Pen(Color.Gray, 2))
                    foreach (MM_OneLine_Element Node in Nodes)
                        if (Node.NodePaths != null)
                        foreach (KeyValuePair<MM_OneLine_Element, GraphicsPath> kvp in Node.NodePaths)
                            if (kvp.Value.IsOutlineVisible(e.X - pnlElements.AutoScrollPosition.X, e.Y - pnlElements.AutoScrollPosition.Y, HitPen))
                            {
                                ToolStripItem newItem = RightClickMenu.Items.Add(Node.ToString() + " to " + kvp.Key.ToString());
                                newItem.Enabled = false;
                                newItem.Tag = kvp;
                                Pokes.Add(kvp.Key, Node);
                            }
                if (Pokes.Count > 0)
                {
                    Point Location = e.Location;
                    RightClickMenu.Tag = Pokes;                                 
                    RightClickMenu.Items.Add("-");
                    RightClickMenu.Items.Add("Add invisible poke point").Tag = CreateXmlElement("PokePoint", "IsVisible", "false", "Bounds", e.X.ToString() + "," + e.Y.ToString() + ",4,4");
                    RightClickMenu.Items.Add("Add vertical jumper").Tag = CreateXmlElement("PokePoint", "IsVisible", "false", "Bounds", e.X.ToString() + "," + e.Y.ToString() + ",11,35", "IsJumper", "true", "Orientation", "Vertical");
                    RightClickMenu.Items.Add("Add horizontal jumper").Tag = CreateXmlElement("PokePoint", "IsVisible", "false", "Bounds", e.X.ToString() + "," + e.Y.ToString() + ",35,11", "IsJumper", "true", "Orientation", "Horizontal");
                    RightClickMenu.Show(pnlElements, e.Location);
                }
            }
            pnlElements.Tag = false;

        }

        /// <summary>
        /// Create an XML element with the specified attributes
        /// </summary>
        /// <param name="Name"></param>
        /// <param name="Attributes"></param>
        /// <returns></returns>
        private XmlElement CreateXmlElement(String Name, params string[] Attributes)
        {
            XmlElement OutElem = OneLine.Document.CreateElement(Name);
            for (int a = 0; a < Attributes.Length; a += 2)
                OutElem.Attributes.Append(OneLine.Document.CreateAttribute(Attributes[a])).Value = Attributes[a + 1];
            return OutElem;
        }

        /// <summary>
        /// Handle the clicking of a menu item
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void RightClickMenu_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
        {

            if (e.ClickedItem.Text == "Add descriptor" || e.ClickedItem.Text == "Add resource node label")
            {
                MM_OneLine_Element Elem = (sender as Control).Tag as MM_OneLine_Element;
                XmlElement xElem = e.ClickedItem.Tag as XmlElement;
                Elem.Definition.AppendChild(xElem);
                AddDescriptor(xElem, CIM, Elem);
                AlignElementLabels(Elem, 'R', Elem.Bounds);
                this.Refresh();
            }
            else if (e.ClickedItem.Tag is CIMListViewItem)
            {
                CIMListViewItem lvi = e.ClickedItem.Tag as CIMListViewItem;
                lvi.EnsureVisible();
                lvElements.SelectedItems.Clear();
                lvi.Selected = true;
            }
            else if (RightClickMenu.Tag is Dictionary<MM_OneLine_Element, MM_OneLine_Element>)
            {
                Dictionary<MM_OneLine_Element, MM_OneLine_Element> Pokes = RightClickMenu.Tag as Dictionary<MM_OneLine_Element, MM_OneLine_Element>;
                XmlElement xElem = e.ClickedItem.Tag as XmlElement;
                foreach (KeyValuePair<MM_OneLine_Element, MM_OneLine_Element> kvp in Pokes)
                {
                    //Create our poke point based on the request
                    MM_OneLine_Element NewPoke = new MM_OneLine_Element(xElem, CIM, BaseElement);
                    if (e.ClickedItem.Text.Contains("jumper"))
                        NewPoke.IsJumper = true;
                    if (e.ClickedItem.Text == "Add vertical jumper")
                        NewPoke.Orientation = MM_OneLine_Element.enumOrientations.Vertical;
                    else if (e.ClickedItem.Text == "Add horizontal jumper")
                        NewPoke.Orientation = MM_OneLine_Element.enumOrientations.Horizontal;
                    else if (e.ClickedItem.Text == "Add invisible poke point")
                        NewPoke.IsVisible = false;
                    NewPoke.CIM = kvp.Value.CIM;
                    NewPoke.KVLevel = kvp.Value.KVLevel;
                    //Now, determine the placement
                    Rectangle FromRect = kvp.Value.Bounds;
                    XmlElement FromElem = kvp.Value.Definition;

                    Point Center1, Center2;
                    PointF Intersection;
                    XmlElement xNodeRef = kvp.Value.Definition.SelectSingleNode("*[@rdfID='" + kvp.Key.CIM.rdfID.ToString() + "']") as XmlElement;
                    if (xNodeRef == null)
                        xNodeRef = kvp.Value.Definition.SelectSingleNode("*[@TEID='" + kvp.Key.CIM.TEID + "']") as XmlElement;
                    foreach (XmlElement xElem2 in xNodeRef.ChildNodes)
                        if (xElem2.Name != "Descriptor" && xElem2.Name != "SecondaryDescriptor")
                        {
                            Rectangle ToRect = Xml_Elements[xElem2].Bounds;
                            MM_DDL_Exporter.GetStraightLine(FromRect, ToRect, out Center1, out Center2, false, false);
                            if (MM_HitTesting.CheckIntersection(NewPoke.Bounds, Center1, Center2, out Intersection))                                                                                           
                            {                             
                                xElem2.ParentNode.InsertBefore(NewPoke.Definition, xElem2);
                                break;
                            }
                            FromRect = ToRect;
                            FromElem = xElem2;
                        }

                    //If we haven't found an interesting match, put it last
                    if (xElem.ParentNode == null)
                        xNodeRef.AppendChild(xElem);
                    CreateOrUpdateElement(NewPoke, NewPoke.Location);
                }
            }
            else if (e.ClickedItem.Tag is KeyValuePair<MM_OneLine_Element, MM_OneLine_Element.enumOrientations>)
            {
                KeyValuePair<MM_OneLine_Element, MM_OneLine_Element.enumOrientations> kvp = (KeyValuePair<MM_OneLine_Element, MM_OneLine_Element.enumOrientations>)e.ClickedItem.Tag;
                kvp.Key.Orientation = kvp.Value;
                kvp.Key.Refresh();
            }
            else
                pnlElements.Tag = e.ClickedItem.Tag;
        }


        /// <summary>
        /// Handle the mouse double-click on the panel
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void pnlElements_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            //If we have an XML element, simulate a drag/drop
            if (pnlElements.Tag is XmlElement)
            {
                MM_OneLine_Element Elem = new MM_OneLine_Element(pnlElements.Tag as XmlElement, CIM, BaseElement);
                Elem.Size = Size.Empty;
                CreateOrUpdateElement(Elem, e.Location);
                Elem.Orientation = MM_OneLine_Element.enumOrientations.Up;
                Elem.ChangeOrientation(MM_OneLine_Element.enumOrientations.Up);
                AlignElementLabels(Elem, 'R', Elem.Bounds);
                pnlElements.Tag = null;
            }

            if (PanelDoubleClicked != null)
                PanelDoubleClicked(sender, new MouseEventArgs(e.Button, e.Clicks, e.X - pnlElements.AutoScrollPosition.X, e.Y - pnlElements.AutoScrollPosition.Y, e.Delta));

        }

        /// <summary>
        /// Handle a drag event into our object
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void pnlElements_DragEnter(object sender, DragEventArgs e)
        {
            Object InObject = e.Data.GetData(e.Data.GetFormats()[0]);
            if (InObject is MM_OneLine_Element[])
                e.Effect = DragDropEffects.All;
            if (PanelMouseMove != null)
                PanelMouseMove(this, new MouseEventArgs(MouseButtons.Left, 0, e.X - pnlElements.AutoScrollPosition.X, e.Y - pnlElements.AutoScrollPosition.Y, 0));
        }


        /// <summary>
        /// Handle a drag/drop event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void pnlElements_DragDrop(object sender, DragEventArgs e)
        {
            //When we drag/drop elements, add them in
            Object InObject = e.Data.GetData(e.Data.GetFormats()[0]);
            Point TargetPoint = pnlElements.PointToClient( new Point(e.X, e.Y));
            if (InObject is MM_OneLine_Element[])
                foreach (MM_OneLine_Element Elem in InObject as MM_OneLine_Element[])
                    try
                    {
                        TargetPoint = CreateOrUpdateElement(Elem, TargetPoint);
                    }
                    catch (Exception)
                    {
                    }
            pnlElements.Refresh();
        }
        #endregion

        #region Mouse handling
        /// <summary>
        /// Handle a mouse up event on an element
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Elem_MouseUp(object sender, MouseEventArgs e)
        {
            MM_OneLine_Element Sender = sender as MM_OneLine_Element;
            if (e.Button == MouseButtons.Right && Sender.CIM != null)
            {
                RightClickMenu.Tag = Sender;
                ShowPopupMenu(Sender.CIM, Sender.PointToScreen(e.Location), Sender);
            }
            /*
            if (e.Button == MouseButtons.Left && (Control.ModifierKeys & Keys.Shift) == Keys.Shift)
            {
                (sender as MM_OneLine_Element).Selected ^= true;
                SelectionChanged(sender, e);
                
            }*/
            pnlElements.Refresh();
        }

        /// <summary>
        /// display a popup menu
        /// </summary>
        /// <param name="Elem"></param>
        /// <param name="Location"></param>
        /// <param name="mElem"></param>
        private void ShowPopupMenu(CIM_Element Elem, Point Location, MM_OneLine_Element mElem)
        {
            RightClickMenu.Items.Clear();
            String OutString = Elem.TypeName.Split(':')[1] + " ";

            if (Elem.TypeName == "cim:ACLineSegment")
                OutString += Elem.ParseSingle("cim:Line", null).Name + (Elem.ParseSingle("cim:Line", null).InLinks.Length == 1 ? "" : Elem.Name);
            else if (Elem.TypeName == "cim:SeriesCompensator")
                OutString += Elem.ParseSingle("cim:Line", null).Name;
            else
                OutString += Elem.Name;

            if (Elem.VoltageLevel != null)
                OutString += " (" + Elem.VoltageLevel.Name + ")";


            if (Elem.TypeName == "cim:ACLineSegment" && Elem.ACSegmentStations[0] == BaseElement)
                RightClickMenu.Items.Add(OutString + " (to substation " + Elem.ACSegmentStations[1].Name + ")");
            else if (Elem.TypeName == "cim:ACLineSegment" && Elem.ACSegmentStations[1] == BaseElement)
                RightClickMenu.Items.Add(OutString + " (to substation " + Elem.ACSegmentStations[0].Name + ")");
            else if (Elem.TypeName == "cim:ACLineSegment")
                RightClickMenu.Items.Add(OutString + " (between substation " + Elem.ACSegmentStations[0].Name + " and " + Elem.ACSegmentStations[1].Name + ")");
            else if (Elem.TypeName == "etx:PricingVector")                            
            {
                CIM_Element TargetElement=null;
                bool PositiveFlow = bool.Parse(Elem["etx:PricingVector.positiveFlowIn"]);
                foreach (CIM_Element Elem2 in Elem.Elements)
                    if (PositiveFlow ^ Elem2.TypeName == "cim:ConnectivityNode")
                        TargetElement = Elem2;
                RightClickMenu.Items.Add(OutString + string.Format(" (towards {0} {1}) - positiveFlow={2}", TargetElement.TypeName.Split(':')[1], TargetElement.Name, PositiveFlow));

            }
            else
                RightClickMenu.Items.Add(OutString);

            //Add in our connected elements 
            if (Elem.Elements.Length > 0)
            {
                RightClickMenu.Items.Add("-");
                RightClickMenu.Items.Add("Connected elements:");
                foreach (CIM_Element Elem2 in Elem.Elements)
                    AddElementToMenu(Elem2, Elem);
            }

            //Add in our nodes, and keep track of elements on the other side
            List<CIM_Element> NodeElems = new List<CIM_Element>();
            if (Elem.Nodes.Length > 0)
            {
                RightClickMenu.Items.Add("-");
                RightClickMenu.Items.Add("Connected nodes:");
                foreach (CIM_Element Node in Elem.Nodes)
                {
                    AddElementToMenu(Node, Elem);
                    foreach (CIM_Element Elem2 in Node.Elements)
                        if (Elem2 != Elem && !NodeElems.Contains(Elem))
                            NodeElems.Add(Elem);
                }
            }

            //Add in our elements on the other side of the nodes
            if (NodeElems.Count > 0)
            {
                RightClickMenu.Items.Add("-");
                RightClickMenu.Items.Add("Elements connected to the element via nodes:");
                foreach (CIM_Element Elem2 in NodeElems)
                    AddElementToMenu(Elem2, Elem);
            }

            //Offer to add in descriptors as needed
            if (Elem.TypeName == "cim:ConnectivityNode")
            {
                bool NeedsLabel = Elem.TypeName != "cim:ConnectivityNode" || Elem.ParseSingle("cim:Terminal>cim:BusbarSection", null) != null;
                bool NeedsRN = Elem.TypeName == "cim:ConnectivityNode" && Elem.ParseSingle("etx:ElectricalBus<etx:ResourceNode", null) != null;
                if (NeedsLabel && !CIM_Descriptors.ContainsKey(Elem))
                    RightClickMenu.Items.Add("Add descriptor").Tag = OneLine.Document.CreateElement("Descriptor");
                else if (!NeedsLabel && CIM_Descriptors.ContainsKey(Elem))
                    RightClickMenu.Items.Add("Remove descriptor").Tag = CIM_Descriptors[Elem];

                if (NeedsRN && !CIM_SecondaryDescriptors.ContainsKey(Elem))
                    RightClickMenu.Items.Add("Add resource node label").Tag = OneLine.Document.CreateElement("SecondaryDescriptor");
                else if (!NeedsRN && CIM_SecondaryDescriptors.ContainsKey(Elem))
                    RightClickMenu.Items.Add("Remove resource node label").Tag = CIM_SecondaryDescriptors[Elem];
            }
            else if (!CIM_Descriptors.ContainsKey(Elem))
                RightClickMenu.Items.Add("Add descriptor").Tag = OneLine.Document.CreateElement("Descriptor");

            //Add in our menu options
            RightClickMenu.Items.Add("-");
            AddOptionToMenu(Elem, "&View in CIM Browser");

            if (mElem != null)
            {
                ToolStripMenuItem OrientationMenu = RightClickMenu.Items.Add("Change orientation") as ToolStripMenuItem;
                OrientationMenu.DropDownItemClicked += RightClickMenu_ItemClicked;
                foreach (MM_OneLine_Element.enumOrientations Orientation in Enum.GetValues(typeof(MM_OneLine_Element.enumOrientations)))
                {
                    ToolStripMenuItem NewOrientation = OrientationMenu.DropDownItems.Add(Orientation.ToString()) as ToolStripMenuItem;
                    NewOrientation.Tag = new KeyValuePair<MM_OneLine_Element, MM_OneLine_Element.enumOrientations>(mElem, Orientation);
                    if (Orientation == mElem.Orientation)
                        NewOrientation.Checked = true;
                    if (!MM_OneLine_Element.AllowedOrientations(mElem.ElemType, BaseElement.TypeName == "cim:Substation" && Elem.TypeName != "cim:SeriesCompensator").Contains(Orientation))
                        NewOrientation.Enabled = false;
                }

                //Write out our XML
                ToolStripMenuItem tsXml = RightClickMenu.Items.Add("Definition: " + mElem.Definition.Name) as ToolStripMenuItem;
                foreach (XmlElement xElem in mElem.Definition.ChildNodes)
                {
                    ToolStripMenuItem tsXml2 = tsXml.DropDownItems.Add(xElem.Name) as ToolStripMenuItem;
                    foreach (XmlAttribute xAttr in xElem.Attributes)
                        tsXml2.DropDownItems.Add(xAttr.Name + ": " + xAttr.Value);
                }
                foreach (XmlAttribute xAttr in mElem.Definition.Attributes)
                    tsXml.DropDownItems.Add(xAttr.Name + ": " + xAttr.Value);
            }
            
           
            /*
            //Locate a node, if we have it
            MM_OneLine_Node BaseNode = null;
            if (Sender is MM_OneLine_PokePoint && Sender is MM_OneLine_PricingVector == false)
                BaseNode = (Sender as MM_OneLine_PokePoint).BaseNode;

            //If we have a node, offer to recheck its connectivity and run a quick validation
            if (BaseNode != null)
            {
                AddOptionToMenu(Sender, "&Re-check node connectivity");
                //First, see if there are any elements referred into the node that doesn't exist
                foreach (MM_Element Elem in (BaseNode.BaseElement as MM_Node).ConnectedElements)
                    if (Elem is MM_PricingVector == false && olView.DisplayElements.ContainsKey(Elem) && !BaseNode.ConnectionPoints.ContainsKey(olView.DisplayElements[Elem]))
                        RightClickMenu.Items.Add(String.Format("Error: {0} {1} {2} is not connected to the node.", Elem.KVLevel.Name, Elem.ElemType.Name, Elem.Name));

                //Now, see if there are any incorrectly referred elements
                foreach (MM_OneLine_Element Elem in BaseNode.ConnectionPoints.Keys)
                    if (Elem is MM_OneLine_Transformer)
                    {
                        bool FoundWinding = false;
                        foreach (MM_OneLine_TransformerWinding Winding in (Elem as MM_OneLine_Transformer).Windings)
                            if (Winding != null && Array.IndexOf<MM_Element>((BaseNode.BaseElement as MM_Node).ConnectedElements, Winding.BaseElement) != -1)
                                FoundWinding = true;
                        if (!FoundWinding)
                            RightClickMenu.Items.Add("Error: Linkage from node " + BaseNode.BaseElement + " to transformer " + Elem.BaseElement.Name + " not found.");
                    }

                    else if (BaseNode.BaseElement != Elem.BaseElement && Array.IndexOf<MM_Element>((BaseNode.BaseElement as MM_Node).ConnectedElements, Elem.BaseElement) == -1)
                        RightClickMenu.Items.Add("Error: Linkage from node " + BaseNode.BaseElement + " to " + Elem.BaseElement.ElemType.Name + " " + Elem.BaseElement.Name + " not found.");
            }
            */
            RightClickMenu.Show(Location);        
        }

        /// <summary>
        /// Add an option to the menu bar, w/ associated tag for callup
        /// </summary>
        /// <param name="Tag">The tag for the menu</param>
        /// <param name="Text">The text of the menu</param>
        private void AddOptionToMenu(Object Tag, String Text)
        {
            ToolStripMenuItem NewItem = RightClickMenu.Items.Add(Text) as ToolStripMenuItem;
            NewItem.Tag = Tag;
            NewItem.Click += new EventHandler(MenuItem_Click);
        }

        /// <summary>
        /// Handle the clicking of a menu item
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MenuItem_Click(object sender, EventArgs e)
        {
            MM_OneLine_Element Elem = RightClickMenu.Tag as MM_OneLine_Element;
            ToolStripMenuItem Sender = sender as ToolStripMenuItem;
            if (Elem != null && Sender.Text.EndsWith("CIM Browser"))
                Elem_MouseDoubleClick(Elem, new MouseEventArgs(MouseButtons.Left, 0, Elem.Left, Elem.Top, 0));
            else if (Sender.Tag is CIMListViewItem)
                (Sender.Tag as CIMListViewItem).Selected = true;
            else if (Elem is MM_OneLine_Element && Sender.Tag is XmlElement)
                pnlElements.Tag = Sender.Tag;
               //CreateOrUpdateElement(Elem, pnlElements.PointToClient(Cursor.Position));
            
            

        }


        /// <summary>
        /// Add an element to the menu collection
        /// </summary>
        /// <param name="Element"></param>
        /// <param name="BaseElement"></param>
        private void AddElementToMenu(CIM_Element Element, CIM_Element BaseElement)
        {
            ToolStripMenuItem ts;
            String OutString = Element.TypeName.Split(':')[1] + " " + (Element.TypeName == "cim:ACLineSegment" ? Element.ParseSingle("cim:Line",null).Name + Element.Name : Element.Name) + (Element.VoltageLevel == null ? "" : " (" + Element.VoltageLevel.Name + ")");

            if (Element.TypeName == "cim:ACLineSegment" && Element.ACSegmentStations[0] == BaseElement)
                ts = RightClickMenu.Items.Add("  " + OutString + " (to substation " + Element.ACSegmentStations[1].Name + ")") as ToolStripMenuItem;
            else if (Element.TypeName == "cim:ACLineSegment" && Element.ACSegmentStations[1] == BaseElement)
                ts = RightClickMenu.Items.Add("  " + OutString + " (to substation " + Element.ACSegmentStations[0].Name + ")") as ToolStripMenuItem;
            else if (Element.TypeName == "cim:ACLineSegment")
                ts = RightClickMenu.Items.Add("  " + OutString + " (between substation " + Element.ACSegmentStations[0].Name + " and " + Element.ACSegmentStations[1].Name + ")") as ToolStripMenuItem;
            else if (Element.Substation == null)
                ts = RightClickMenu.Items.Add("  " + OutString + " (substation unknown)") as ToolStripMenuItem;
            else if (Element.Substation != BaseElement)
                ts = RightClickMenu.Items.Add("  " + OutString + " (in substation " + Element.Substation.Name + ")") as ToolStripMenuItem;
            else
                ts = RightClickMenu.Items.Add("  " + OutString) as ToolStripMenuItem;
            ts.Name = Element.TEID;
            
            //Now, link the element to the display
            MM_OneLine_Element Elem2;
            CIMListViewItem lvi;
            if ((lvi = lvElements.Items[Element.rdfID.ToString()] as CIMListViewItem) != null)
                if (CIM_Elements.TryGetValue(Element, out Elem2))
                {
                    ts.ForeColor = Color.DarkGreen;

                    ts.Tag = lvi;
                    ts.Click += MenuItem_Click;
                }
                else
                {
                    XmlElement NewElem = CreateXmlElement(Element.ElemType, "rdfID", Element.rdfID.ToString(), "Orientation", "Horizontal");

                    //If we're an transformer, add in our windings
                    if (Element.TypeName == "cim:PowerTransformer")
                    {
                        CIM_Element[] Windings = Element.Parse("cim:TransformerWinding", null);
                        NewElem.AppendChild(CreateXmlElement("Winding", "rdfID", Windings[0].rdfID.ToString(), "Bounds", "0,0,13,25"));
                        NewElem.AppendChild(CreateXmlElement("Winding", "rdfID", Windings[1].rdfID.ToString(), "Bounds", "14,0,13,25"));
                    }

                    //If we're a node, determine what labels need to be present, and add them
                    bool NeedsLabel = Element.TypeName != "cim:ConnectivityNode" || Element.ParseSingle("cim:Terminal>cim:BusbarSection", null) != null;
                    bool NeedsRN = Element.TypeName == "cim:ConnectivityNode" && Element.ParseSingle("etx:ElectricalBus<etx:ResourceNode", null) != null;
                    if (NeedsLabel)
                        NewElem.AppendChild(CreateXmlElement("Descriptor"));
                    if (NeedsRN)
                        NewElem.AppendChild(CreateXmlElement("SecondaryDescriptor"));
                    ts.Tag = NewElem;
                    ts.Click += MenuItem_Click;

                }
            else
                ts.Enabled = false;
        

            //Now, add a transformer winding if needed
            if (Element.TypeName == "cim:TransformerWinding" && !RightClickMenu.Items.ContainsKey(Element.ParseSingle("cim:PowerTransformer",null).TEID))
                AddElementToMenu(Element.ParseSingle("cim:PowerTransformer",null), BaseElement);
        }

        /// <summary>
        /// Handle a mouse movement event on an element
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Elem_MouseMove(object sender, MouseEventArgs e)
        {
            Rectangle OutRect = Rectangle.Empty;
                    
            MM_OneLine_Element Sender = sender as MM_OneLine_Element;
            if (e.Button == MouseButtons.Left && MouseDownPoint.X > -1 && !MouseDownPoint.IsEmpty)
            {
                Point Delta = new Point(e.X - MouseDownPoint.X, e.Y - MouseDownPoint.Y);

                //If we can resize our element, handle the updates
                if (Sender.Cursor == Cursors.Default)
                {
                    foreach (MM_OneLine_Element Elem in pnlElements.Controls)
                        if (Elem.Selected || (Elem.ParentElement != null && Elem.ParentElement.Selected))                            
                                OutRect = UnionRectangle(OutRect,MoveElement(Elem, e.X - MouseDownPoint.X, e.Y - MouseDownPoint.Y, 0, 0));
                    InvalidatePanel(OutRect);
                }
                else
                {
                    Rectangle OrigBounds = (Rectangle)Sender.Tag;                    
                    foreach (MM_OneLine_Element Elem in pnlElements.Controls)
                        if (Elem.CanSize && (Elem.Selected || (Elem.ParentElement != null && Elem.ParentElement.Selected)))
                        {
                            Rectangle Resp = Elem.Bounds;
                            if (Sender.Cursor == Cursors.PanNorth)
                                Resp = MoveElement(Elem, 0, OrigBounds.Top + Delta.Y - Elem.Top, 0, OrigBounds.Height - Delta.Y - Elem.Height);
                            else if (Sender.Cursor == Cursors.PanSouth)
                                Resp = MoveElement(Elem, 0, 0, 0, OrigBounds.Height + Delta.Y - Elem.Height);
                            else if (Sender.Cursor == Cursors.PanWest && Delta.X != 0)
                                Resp = MoveElement(Elem, OrigBounds.Left + Delta.X - Elem.Left, 0, OrigBounds.Width - Delta.X - Elem.Width, 0);
                            else if (Sender.Cursor == Cursors.PanEast && Delta.X != 0)
                                Resp = MoveElement(Elem, 0, 0, OrigBounds.Width + Delta.X - Elem.Width, 0);
                            OutRect = UnionRectangle(OutRect, Resp);
                        }
                    InvalidatePanel(OutRect);
                }
            }
            else if (Sender.CanSize)
            {
                bool Horiz = Sender.Orientation == MM_OneLine_Element.enumOrientations.Unknown || Sender.Orientation == MM_OneLine_Element.enumOrientations.Horizontal;
                bool Vert = Sender.Orientation == MM_OneLine_Element.enumOrientations.Unknown || Sender.Orientation == MM_OneLine_Element.enumOrientations.Vertical;

                if (Horiz && e.X >= Sender.Width - 2)
                    Sender.Cursor = Cursors.PanEast;
                else if (Horiz && e.X <= 2)
                    Sender.Cursor = Cursors.PanWest;
                else if (Vert && e.Y >= Sender.Height - 2)
                    Sender.Cursor = Cursors.PanSouth;
                else if (Vert && e.Y <= 2)
                    Sender.Cursor = Cursors.PanNorth;
                else
                    Sender.Cursor = Cursors.Default;
            }
            else
                Sender.Cursor = Cursors.Default;

            if (PanelMouseMove != null)
                PanelMouseMove(sender, new MouseEventArgs(e.Button, e.Clicks, e.X + Sender.Left - pnlElements.AutoScrollPosition.X, e.Y + Sender.Top - pnlElements.AutoScrollPosition.Y, e.Delta));
            
        }

        /// <summary>
        /// Handle a mouse double-click event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Elem_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            MM_OneLine_Element Sender = sender as MM_OneLine_Element;
            if (Sender != null && Sender.CIM != null)
            {
                frmCIMBrowser NewForm = new frmCIMBrowser(Sender.CIM.CIM);

                //Try and find our owner form
                WeifenLuo.WinFormsUI.Docking.DockPanel TargetPanel = null;
                Form FormToCheck = this.ParentForm;
                do
                {
                    foreach (Control ctl in FormToCheck.Controls)
                        if (ctl is WeifenLuo.WinFormsUI.Docking.DockPanel)
                            TargetPanel = ctl as WeifenLuo.WinFormsUI.Docking.DockPanel;
                    FormToCheck = FormToCheck.ParentForm;
                } while (TargetPanel == null && FormToCheck != null);

                if (TargetPanel == null)
                {
                    NewForm.CIM.ClearElements();
                    NewForm.CIM.AddElement(Sender.CIM, null);
                    NewForm.Show();
                }
                else
                {
                    NewForm.Dock = DockStyle.Right;
                    NewForm.Show(Sender.CIM, TargetPanel);
                }

            }
        }

        /// <summary>
        /// Handle a mouse down event on an element
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Elem_MouseDown(object sender, MouseEventArgs e)
        {
            pnlElements.Tag = true;
            MM_OneLine_Element Sender = (sender as MM_OneLine_Element);
            if (e.Button == MouseButtons.Left)
            {
                DragRectangle = Rectangle.Empty;
                MouseDownPoint = e.Location;
                if ((Control.ModifierKeys & Keys.Shift) == Keys.Shift)
                {
                    Sender.Selected ^= true;
                    CIMListViewItem lvI;
                    if (Sender.CIM != null && Xml_Items.TryGetValue(Sender.CIM, out lvI))
                    {
                        lvI.Selected = Sender.Selected;
                        lvI.EnsureVisible();
                    }
                    RefreshElements(Sender);
                }
                else
                {
                    if (!Sender.Selected)
                    {
                        List<MM_OneLine_Element> Deselected = new List<MM_OneLine_Element>();
                        foreach (MM_OneLine_Element Elem in pnlElements.Controls)
                            if (Elem.Selected && !Elem.Equals(sender))
                            {
                                Deselected.Add(Elem);
                                Elem.Selected = false;
                            }
                            else if (Elem.Equals(sender))
                            {
                                Elem.Selected = true;
                                TreeNode SelNode;
                                if (Xml_Nodes.TryGetValue(Elem, out SelNode))
                                    tvXml.SelectedNode = SelNode;
                                
                                CIMListViewItem lvI;
                                if (Elem.CIM != null && Xml_Items.TryGetValue(Elem.CIM, out lvI))
                                {
                                    lvI.EnsureVisible();
                                    lvElements.SelectedItems.Clear();
                                    lvI.Selected = true;
                                    //lvElements.FocusedItem = Xml_Items[Elem.CIM];
                                }
                            }
                        SelectionChanged(sender, e);
                        RefreshElements(Deselected.ToArray());
                        
                    }

                    if (Sender.Selected && Sender.Cursor != Cursors.Default)
                        Sender.Tag = Sender.Bounds;
                }
            }
            pnlElements.Tag = false;
        }

        /// <summary>
        /// Provide a ListViewControl that handles CIM
        /// </summary>
        private class CIMListViewItem : ListViewItem
        {
            /// <summary>The element associted with the list view item</summary>
            public CIM_Element AssociatedElement;

            /// <summary>
            /// 
            /// </summary>
            /// <param name="ElemName"></param>
            /// <param name="BaseElement"></param>
            /// <param name="Group"></param>
            /// <param name="Descriptors"></param>
            /// <param name="Elements"></param>
            /// <param name="SecondaryDescriptors"></param>            
            public CIMListViewItem(String ElemName, CIM_Element BaseElement, ListViewGroup Group, Dictionary<CIM_Element, MM_OneLine_Element> Elements, Dictionary<CIM_Element, MM_OneLine_Element> Descriptors, Dictionary<CIM_Element, MM_OneLine_Element> SecondaryDescriptors)
            {
                this.AssociatedElement = BaseElement;
                Text = ElemName;
                CIM_Element Voltage = BaseElement.VoltageLevel;
                if (Voltage != null)
                    SubItems.Add(Voltage.Name);
                else
                    SubItems.Add("");

                UInt64 TEID;
                if (UInt64.TryParse(BaseElement.TEID, out TEID))
                    SubItems.Add(TEID.ToString("#,##0"));
                else
                    SubItems.Add("**MISSING**");
                SubItems.Add(BaseElement.rdfID.ToString());
                this.Group = Group;
                this.Name = BaseElement.rdfID.ToString();
                Tag = BaseElement;                
            }

            /// <summary>
            /// Refresh the state of our element
            /// </summary>
            /// <param name="Descriptors"></param>
            /// <param name="Elements"></param>
            /// <param name="SecondaryDescriptors"></param>            
            public void Refresh(Dictionary<CIM_Element, MM_OneLine_Element> Elements, Dictionary<CIM_Element, MM_OneLine_Element> Descriptors, Dictionary<CIM_Element, MM_OneLine_Element> SecondaryDescriptors)
            {
                //Determine whether we need a label 
                bool NeedsLabel = AssociatedElement.TypeName != "cim:ConnectivityNode" || AssociatedElement.ParseSingle("cim:Terminal>cim:BusbarSection", null) != null;
                bool NeedsRN = AssociatedElement.TypeName == "cim:ConnectivityNode" && AssociatedElement.ParseSingle("etx:ElectricalBus<etx:ResourceNode", null) != null;
                
                
                bool NodesOk = true;
                if (AssociatedElement.TypeName == "cim:ConnectivityNode")
                {
                    foreach (CIM_Element NodeElem in AssociatedElement.Elements)
                        if (ListView.Items[NodeElem.rdfID.ToString()] != null && !Elements.ContainsKey(NodeElem))
                            NodesOk = false;
                }
                else
                {
                    foreach (CIM_Element ElemNode in AssociatedElement.Nodes)
                        if (ListView.Items[ElemNode.rdfID.ToString()] != null && !Elements.ContainsKey(ElemNode))
                            NodesOk = false;
                }
      



                //First, check to see if our element exists
                if (!Elements.ContainsKey(AssociatedElement))
                    this.ForeColor = Color.Red;
                else if (!NodesOk)
                    this.ForeColor = Color.Yellow;
                else if (NeedsLabel  ^ Descriptors.ContainsKey(AssociatedElement))
                    this.ForeColor = Color.Purple;
                else if (NeedsRN  ^ SecondaryDescriptors.ContainsKey(AssociatedElement))
                    this.ForeColor = Color.Purple;
                else
                    this.ForeColor = Color.White; 
                
                                                   
            }
        }


        /// <summary>
        /// Refresh every element, and its releated components
        /// </summary>
        /// <param name="AdditionalElements"></param>
        private void RefreshElements(params MM_OneLine_Element[] AdditionalElements)
        {
            Rectangle OutRect = Rectangle.Empty;

            List<MM_OneLine_Element> Elems = SelectedElements;
            Elems.AddRange(AdditionalElements);
            foreach (MM_OneLine_Element Elem in Elems)
            {
                OutRect = UnionRectangle(OutRect, Elem.Bounds);
                if (Elem.Descriptor != null)
                    OutRect = UnionRectangle(OutRect, Elem.Descriptor.Bounds);
                if (Elem.SecondaryDescriptor != null)
                    OutRect = UnionRectangle(OutRect, Elem.SecondaryDescriptor.Bounds);
            }
            InvalidatePanel(OutRect);
        }
        #endregion


        /// <summary>
        /// Move one or more elements, then refresh everything
        /// </summary>
        /// <param name="Elements"></param>
        /// <param name="X"></param>
        /// <param name="Y"></param>
        private bool MoveElements(IEnumerable<MM_OneLine_Element> Elements, int X, int Y)
        {
            Rectangle OutRect = Rectangle.Empty;
            foreach (MM_OneLine_Element Elem in Elements)
                OutRect = UnionRectangle(OutRect, MoveElement(Elem, X, Y, 0, 0));
            InvalidatePanel(OutRect);
            return true;
        }

        /// <summary>
        /// Handle the user's key presses to detect arrow movement of the selected one-line element
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="keyData"></param>
        /// <returns></returns>
        protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
        {

            if (keyData == Keys.Up)
                return MoveElements(SelectedElementsAndTheirDescriptors,0,-1);
            else if (keyData == Keys.Down)
                return MoveElements(SelectedElementsAndTheirDescriptors, 0, 1);
            else if (keyData == Keys.Left)
                return MoveElements(SelectedElementsAndTheirDescriptors, -1,0);
            else if (keyData == Keys.Right)
                return MoveElements(SelectedElementsAndTheirDescriptors, 1, 0);
            else if (keyData == (Keys.Up | Keys.Control))
                return MoveElements(SelectedElementsAndTheirDescriptors, 0, -10);
            else if (keyData == (Keys.Down | Keys.Control))
                return MoveElements(SelectedElementsAndTheirDescriptors, 0, 10);
            else if (keyData == (Keys.Left | Keys.Control))
                return MoveElements(SelectedElementsAndTheirDescriptors, -10, 0);
            else if (keyData == (Keys.Right | Keys.Control))
                return MoveElements(SelectedElementsAndTheirDescriptors, 10, 0);
            else if (keyData == (Keys.Control | Keys.A))
            {
                foreach (MM_OneLine_Element Elem in pnlElements.Controls)
                    Elem.Selected = true;
                pnlElements.Refresh();
                return true;
            }
            else if (keyData == (Keys.Control | Keys.R))
            {
                btnRotate_Click(btnRotate, EventArgs.Empty);
                return true;
            }
            else if (keyData == (Keys.Control | Keys.F))
            {
                btnFlipTransformerWindings_Click(btnFlipTransformerWindings, EventArgs.Empty);
                return true;
            }
            else if (keyData == (Keys.Control | Keys.W))
            {
                btnDrawArrow_Click(btnDrawArrow, EventArgs.Empty);
                return true;
            }
            else if (keyData == (Keys.Alt | Keys.D7))
            {
                btnValidate_Click(btnValidate, EventArgs.Empty);
                return true;
            }
            else if (keyData == (Keys.Control | Keys.D1))
            {
                btnAlignLeft_Click(btnAlignLeft, EventArgs.Empty);
                return true;
            }
            else if (keyData == (Keys.Control | Keys.D2))
            {
                btnAlignTop_Click(btnAlignTop, EventArgs.Empty);
                return true;
            }
            else if (keyData == (Keys.Control | Keys.D3))
            {
                btnAlignRight_Click(btnAlignRight, EventArgs.Empty);
                return true;
            }
            else if (keyData == (Keys.Control | Keys.D4))
            {
                btnAlignBottom_Click(btnAlignBottom, EventArgs.Empty);
                return true;
            }
            else if (keyData == (Keys.Control | Keys.D5))
            {
                btnCenterHorizontally_Click(btnCenterHorizontally, EventArgs.Empty);
                return true;
            }
            else if (keyData == (Keys.Control | Keys.D6))
            {
                btnCenterVertically_Click(btnCenterVertically, EventArgs.Empty);
                return true;
            }
            else if (keyData == Keys.Delete)
            {
                StringBuilder sB = new StringBuilder();
                foreach (MM_OneLine_Element Elem in SelectedElements)
                {
                    MM_OneLine_Element.enumElemTypes ElemType = Elem.ElemType;
                    sB.AppendLine("  " + ElemType.ToString() + " " + Elem.Text);
                }
                if (sB.Length > 0 && MessageBox.Show("Are you sure you'd like to delete the following objects?\n" + sB.ToString(), Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                {
                    foreach (MM_OneLine_Element Elem in SelectedElements)
                        RemoveElement(Elem);
                    pnlElements.Refresh();
                }
                return true;
            }
            else
                return base.ProcessCmdKey(ref msg, keyData);
        }

        /// <summary>
        /// Move an element by a specified number of pixels
        /// </summary>
        /// <param name="Elem"></param>
        /// <param name="X"></param>
        /// <param name="Y"></param>
        /// <param name="Height"></param>
        /// <param name="Width"></param>
        /// <returns>The rectangle of our target elements, involved labels if arrows are present, and </returns>
        private Rectangle MoveElement(MM_OneLine_Element Elem, int X, int Y, int Width, int Height)
        {
            Rectangle OutRect = UnionRectangle(Elem.Bounds, Elem.Bounds = new Rectangle(Elem.Left + X, Elem.Top + Y, Elem.Width + Width, Elem.Height + Height));
            
            //If our element has export values, recompute them.
            if (Elem.ElemType == MM_OneLine_Element.enumElemTypes.Descriptor || Elem.ElemType == MM_OneLine_Element.enumElemTypes.SecondaryDescriptor)
            {
                foreach (MM_OneLine_Value Val in Elem.ExportValues)
                    OutRect = UnionRectangle(OutRect, new Rectangle(Val.Left + pnlElements.AutoScrollPosition.X, Val.Top + pnlElements.AutoScrollPosition.Y, Val.Width, Val.Height));
                
                Elem.ExportValues = MM_OneLine_Element.BuildExportPointsWithLocations(Elem.ParentElement.Definition, Elem.ExportValues, new Rectangle(Point.Subtract(Elem.ParentElement.Location, new Size(pnlElements.AutoScrollPosition)), Elem.ParentElement.Size), new Rectangle(Point.Subtract(Elem.Location, new Size(pnlElements.AutoScrollPosition)), Elem.Size));
                foreach (MM_OneLine_Value Val in Elem.ExportValues)
                    OutRect = UnionRectangle(OutRect, new Rectangle(Val.Left + pnlElements.AutoScrollPosition.X, Val.Top + pnlElements.AutoScrollPosition.Y, Val.Width, Val.Height));
            }

            //Update our bounds attribute 
            MM_Serializable.WriteAttribute(Elem.Definition, "Bounds", new Rectangle(Elem.Left - pnlElements.AutoScrollPosition.X, Elem.Top - pnlElements.AutoScrollPosition.Y, Elem.Width, Elem.Height));

            //If we're a node, refresh all graphics paths
            GraphicsPath gp;
             
            if (Elem.ElemType == MM_OneLine_Element.enumElemTypes.Node && Elem.NodePaths != null)
            {
                //Update our rectangle on all pre-shift points                
                foreach (GraphicsPath gp2 in Elem.NodePaths.Values)
                    OutRect = UnionRectangle(OutRect, gp2);

                foreach (XmlElement xElem in Elem.Definition.ChildNodes)
                    if (xElem.Name != "Descriptor" && xElem.Name != "SecondaryDescriptor")
                    {
                        XmlElement xTarget = null;
                        if (xElem.HasAttribute("rdfID"))
                            xTarget = xElem.OwnerDocument.SelectSingleNode("//*[@rdfID='" + xElem.Attributes["rdfID"].Value + "']") as XmlElement;
                        if (xTarget == null)
                            xTarget = xElem.OwnerDocument.SelectSingleNode("//*[@BaseElement.TEID='" + xElem.Attributes["TEID"].Value + "']") as XmlElement;                        
                        if (xTarget != null)
                            OutRect = UnionRectangle(OutRect, Elem.RebuildNodePath(Xml_Elements[xTarget], xElem));                        
                    }
            }
            else if (Elem.ElemType == MM_OneLine_Element.enumElemTypes.PokePoint || Elem.ElemType == MM_OneLine_Element.enumElemTypes.PricingVector)
            {
                MM_OneLine_Element NodeElem = Xml_Elements[Elem.Definition.ParentNode.ParentNode as XmlElement];
                MM_OneLine_Element TargetElem = Xml_Elements[Elem.Definition.OwnerDocument.SelectSingleNode("//*[@BaseElement.TEID='" + Elem.Definition.ParentNode.Attributes["TEID"].Value + "']") as XmlElement];
                if (NodeElem.NodePaths != null && NodeElem.NodePaths.TryGetValue(TargetElem, out gp))
                    OutRect = UnionRectangle(OutRect, gp);
                OutRect = UnionRectangle(OutRect, NodeElem.RebuildNodePath(TargetElem, Elem.Definition.ParentNode as XmlElement));                                
            }
            else
            {                
                foreach (MM_OneLine_Element Node in Nodes)                    
                    if (Node.NodePaths != null && Node.NodePaths.TryGetValue(Elem, out gp))
                    {
                        OutRect= UnionRectangle(OutRect, gp);
                        
                        //First, try and find our other element by rdf:ID
                        XmlElement OtherElem = null;
                        if (Elem.Definition.HasAttribute("rdfID"))
                            OtherElem = Node.Definition.SelectSingleNode("*[@rdfID='" + Elem.Definition.Attributes["rdfID"].Value + "']") as XmlElement;
                        if (OtherElem == null)
                            OtherElem = Node.Definition.SelectSingleNode("*[@TEID='" + Elem.Definition.Attributes["BaseElement.TEID"].Value + "']") as XmlElement;
                        OutRect = UnionRectangle(OutRect, Node.RebuildNodePath(Elem, OtherElem));                        
                    }
            }
            return OutRect;
        }

        /// <summary>
        /// Combine a rectangle and graphics path
        /// </summary>
        /// <param name="OutRect"></param>
        /// <param name="gp"></param>
        /// <returns></returns>
        private Rectangle UnionRectangle(Rectangle OutRect, GraphicsPath gp)
        {
            PointF Min = new PointF(OutRect.Left, OutRect.Top);
            PointF Max = new PointF(OutRect.Right, OutRect.Bottom);
            foreach (PointF pt in gp.PathPoints)
            {
                Min.X = Math.Min(pt.X, Min.X);
                Min.Y = Math.Min(pt.Y, Min.Y);
                Max.X = Math.Max(pt.X, Min.X);
                Max.Y = Math.Max(pt.Y, Min.Y);
            }
            return new Rectangle((int)Math.Floor(Min.X), (int)Math.Floor(Min.Y), (int)Math.Ceiling(Max.X), (int)Math.Ceiling(Max.Y));
       }
 
        /// <summary>
        /// Combine two rectangles or a return the non-empty one.
        /// </summary>
        /// <param name="InRect"></param>
        /// <param name="NewRect"></param>
        /// <returns></returns>
        private Rectangle UnionRectangle(Rectangle InRect, Rectangle NewRect)
        {
            Rectangle OutRect;
            if (InRect.IsEmpty)
                OutRect = NewRect;
            else if (NewRect.IsEmpty)
                OutRect = InRect;
            else
                OutRect =Rectangle.Union(InRect, NewRect);
            OutRect.X -= 2;
            OutRect.Y -= 2;
            OutRect.Width += 4;
            OutRect.Height+= 4;
            return OutRect;
        }

        #region Element creation and removal
        /// <summary>
        /// Remove an element from the display
        /// </summary>
        /// <param name="Elem"></param>
        private void RemoveElement(MM_OneLine_Element Elem)
        {
            //First, remove the element from the display, along with its descriptors
            pnlElements.Controls.Remove(Elem);
            pnlElements.Controls.Remove(Elem.Descriptor);
            pnlElements.Controls.Remove(Elem.SecondaryDescriptor);


            //Remove the element from our element collection
            Xml_Elements.Remove(Elem.Definition);
            if (Elem.CIM != null && Elem.ElemType != MM_OneLine_Element.enumElemTypes.PokePoint)
            {
                if (Elem.ElemType != MM_OneLine_Element.enumElemTypes.Descriptor && Elem.ElemType != MM_OneLine_Element.enumElemTypes.SecondaryDescriptor)
                    CIM_Elements.Remove(Elem.CIM);

                if (Elem.Descriptor != null || Elem.ElemType == MM_OneLine_Element.enumElemTypes.Descriptor)
                    CIM_Descriptors.Remove(Elem.CIM);

                if (Elem.SecondaryDescriptor != null || Elem.ElemType == MM_OneLine_Element.enumElemTypes.SecondaryDescriptor)
                    CIM_SecondaryDescriptors.Remove(Elem.CIM);               
            }

            //Orphan our element, and make sure no nodes are intersecting                        
            SelectionChanged(this, EventArgs.Empty);
            GraphicsPath g;
            if (Elem.ElemType == MM_OneLine_Element.enumElemTypes.Node)
            {
                Nodes.Remove(Elem);

                List<MM_OneLine_Element> ConnectedElems = new List<MM_OneLine_Element>();
                foreach (MM_OneLine_Element ElemToRemove in pnlElements.Controls)
                    if (ElemToRemove.ElemType == MM_OneLine_Element.enumElemTypes.PokePoint && ElemToRemove.Definition.ParentNode.ParentNode == Elem.Definition)
                        ConnectedElems.Add(ElemToRemove);
                    else if (ElemToRemove.ElemType == MM_OneLine_Element.enumElemTypes.PricingVector && ElemToRemove.Definition.ParentNode.ParentNode == Elem.Definition)
                        ConnectedElems.Add(ElemToRemove);

                foreach (MM_OneLine_Element ElemToRemove in ConnectedElems)
                    {
                        pnlElements.Controls.Remove(ElemToRemove);
                        Xml_Elements.Remove(ElemToRemove.Definition);

                        if (ElemToRemove.Descriptor != null)
                            CIM_Descriptors.Remove(ElemToRemove.CIM);
                        pnlElements.Controls.Remove(ElemToRemove.Descriptor);

                        if (ElemToRemove.SecondaryDescriptor != null)
                            CIM_SecondaryDescriptors.Remove(ElemToRemove.CIM);
                        pnlElements.Controls.Remove(ElemToRemove.SecondaryDescriptor);


                        if (ElemToRemove.ElemType == MM_OneLine_Element.enumElemTypes.PricingVector)
                        {
                            CIM_Elements.Remove(ElemToRemove.CIM);
                            Xml_Elements.Remove(ElemToRemove.Definition);
                        }
                    }                                                          
                Elem.Definition.ParentNode.RemoveChild(Elem.Definition);
            }
            else if (Elem.ElemType == MM_OneLine_Element.enumElemTypes.PokePoint || Elem.ElemType == MM_OneLine_Element.enumElemTypes.PricingVector)
                try
                {
                    MM_OneLine_Element Node = Xml_Elements[Elem.Definition.ParentNode.ParentNode as XmlElement];
                    XmlElement xTarget = Elem.Definition.ParentNode as XmlElement;
                    Elem.Definition.ParentNode.RemoveChild(Elem.Definition);
                    MM_OneLine_Element Elem2 = Xml_Elements[Elem.Definition.OwnerDocument.SelectSingleNode("//*[@BaseElement.TEID='" + xTarget.Attributes["TEID"].Value + "']") as XmlElement];
                    Node.RebuildNodePath(Elem2, xTarget);
                }
                catch { }
            else
            {
                foreach (MM_OneLine_Element Node in Nodes)
                    if (Node.NodePaths != null && Node.NodePaths.TryGetValue(Elem, out g))
                    {
                        g.Dispose();
                        Node.NodePaths.Remove(Elem);
                        
                        //Find the linkage from node to element
                        XmlElement NodeLinkage = Node.Definition.SelectSingleNode("*[@rdfID='" + Elem.CIM.rdfID.ToString() + "']") as XmlElement;
                        if (NodeLinkage == null)
                            NodeLinkage = Node.Definition.SelectSingleNode("*[@TEID='" + Elem.CIM.TEID + "']") as XmlElement;
                        if (NodeLinkage != null)
                            Node.Definition.RemoveChild(NodeLinkage);
                        

                        //Build our list of node path elements to be removed
                        List<MM_OneLine_Element> ConnectedElems = new List<MM_OneLine_Element>();
                        foreach (MM_OneLine_Element ElemToRemove in pnlElements.Controls)
                            if (ElemToRemove.ElemType == MM_OneLine_Element.enumElemTypes.PokePoint && ElemToRemove.Definition.ParentNode.Attributes["TEID"].Value == Elem.Definition.Attributes["BaseElement.TEID"].Value)
                                ConnectedElems.Add(ElemToRemove);
                            else if (ElemToRemove.ElemType == MM_OneLine_Element.enumElemTypes.PricingVector && ElemToRemove.Definition.ParentNode.Attributes["TEID"].Value == Elem.Definition.Attributes["BaseElement.TEID"].Value)
                                ConnectedElems.Add(ElemToRemove);

                        foreach (MM_OneLine_Element ElemToRemove in ConnectedElems)
                        {
                            pnlElements.Controls.Remove(ElemToRemove);
                            Xml_Elements.Remove(ElemToRemove.Definition);

                            if (ElemToRemove.Descriptor != null)
                                CIM_Descriptors.Remove(ElemToRemove.CIM);
                            pnlElements.Controls.Remove(ElemToRemove.Descriptor);

                            if (ElemToRemove.SecondaryDescriptor != null)
                                CIM_SecondaryDescriptors.Remove(ElemToRemove.CIM);
                            pnlElements.Controls.Remove(ElemToRemove.SecondaryDescriptor);


                            if (ElemToRemove.ElemType == MM_OneLine_Element.enumElemTypes.PricingVector)
                            {
                                CIM_Elements.Remove(ElemToRemove.CIM);
                                Xml_Elements.Remove(ElemToRemove.Definition);
                            }
                        }    

                        CIMListViewItem lvNode = lvElements.Items[Node.CIM.rdfID.ToString()] as CIMListViewItem;
                        if (lvNode != null)
                            lvNode.Refresh(CIM_Elements, CIM_Descriptors, CIM_SecondaryDescriptors);
                    }
            }

            if (Elem.Definition.ParentNode != null)
                Elem.Definition.ParentNode.RemoveChild(Elem.Definition);

            //Refresh our CIM information on our element
            if (Elem.CIM != null)
            {
                CIMListViewItem lv = lvElements.Items[Elem.CIM.rdfID.ToString()] as CIMListViewItem;
                if (lv != null)
                    lv.Refresh(CIM_Elements, CIM_Descriptors, CIM_SecondaryDescriptors);
            }
        }

        /// <summary>
        /// Create or update a new CIM element
        /// </summary>
        /// <param name="Elem"></param>
        /// <param name="StartPoint"></param>
        private Point CreateOrUpdateElement(MM_OneLine_Element Elem, Point StartPoint)
        {
            Elem.Location = StartPoint;
            if (Elem.Parent == null)
            {
                pnlElements.Controls.Add(Elem);
                Elem.MouseMove += Elem_MouseMove;
                Elem.MouseUp += Elem_MouseUp;
                Elem.MouseDown += Elem_MouseDown;
                Elem.MouseDoubleClick += Elem_MouseDoubleClick;
            }

            //Make sure our element is allocated to the proper dictionaries
            if (Elem.CIM != null && Elem.ElemType == MM_OneLine_Element.enumElemTypes.Descriptor && !CIM_Descriptors.ContainsKey(Elem.CIM))
                CIM_Descriptors.Add(Elem.CIM, Elem);
            else if (Elem.CIM != null && Elem.ElemType == MM_OneLine_Element.enumElemTypes.SecondaryDescriptor && !CIM_SecondaryDescriptors.ContainsKey(Elem.CIM))
                CIM_SecondaryDescriptors.Add(Elem.CIM, Elem);
            else if (Elem.CIM != null && !CIM_Elements.ContainsKey(Elem.CIM))
                CIM_Elements.Add(Elem.CIM, Elem);
            if (!Xml_Elements.ContainsKey(Elem.Definition))
                Xml_Elements.Add(Elem.Definition, Elem);

            //Make sure our XML definitions are properly identified
            if (Elem.Definition.ParentNode == null)
                if (Elem.ElemType == MM_OneLine_Element.enumElemTypes.PricingVector)
                {
                    //Find our node->elem linkage, and insert
                    CIM_Element Node = null, Other = null;
                    foreach (CIM_Element Elem2 in Elem.CIM.Elements)
                        if (Elem2.ElemType == "Node")
                            Node = Elem2;
                        else
                            Other = Elem2;
                    
                    //Check to see whether our other element is a winding
                    if (Other.TypeName == "cim:TransformerWinding")
                        Other = Other.ParseSingle("cim:PowerTransformer", null);

                    XmlElement NodeLink = CIM_Elements[Node].Definition.SelectSingleNode(CIM_Elements[Other].Definition.Name + "[@rdfID='" + CIM_Elements[Other].CIM.rdfID.ToString() + "']") as XmlElement;
                    if (NodeLink == null)
                        NodeLink = CIM_Elements[Node].Definition.SelectSingleNode(CIM_Elements[Other].Definition.Name + "[@TEID='" + CIM_Elements[Other].CIM.TEID + "']") as XmlElement;
                    NodeLink.AppendChild(Elem.Definition);
                }
                else if (Elem.ElemType == MM_OneLine_Element.enumElemTypes.Node)
                    OneLine.Document.DocumentElement["Nodes"].AppendChild(Elem.Definition);
                else if (Elem.ElemType == MM_OneLine_Element.enumElemTypes.Label || Elem.ElemType == MM_OneLine_Element.enumElemTypes.Circle || Elem.ElemType == MM_OneLine_Element.enumElemTypes.Rectangle || Elem.ElemType == MM_OneLine_Element.enumElemTypes.ArrowLine)
                    OneLine.Document.DocumentElement["Unlinked_Elements"].AppendChild(Elem.Definition);
                else
                    OneLine.Document.DocumentElement["Elements"].AppendChild(Elem.Definition);

            //If a label, ask for text, if an image, ask for image
            if (Elem.ElemType == MM_OneLine_Element.enumElemTypes.Label && String.IsNullOrEmpty(Elem.Text))
            {
                String Text = Elem.Text;
                if (MM_Input_Box.InputBox("Please eneter the text for the label", Application.ProductName, ref Text) == DialogResult.OK)
                {
                    Elem.Text = Text;
                    using (Graphics g = Graphics.FromHwnd(IntPtr.Zero))
                        Elem.Size = Size.Ceiling(g.MeasureString(Elem.Text, Elem.Font));
                }
            } 
            else if (Elem.ElemType == MM_OneLine_Element.enumElemTypes.Image && Elem.BackgroundImage == null)
                using (OpenFileDialog oFd = new OpenFileDialog())
                {
                    oFd.Title = "Select an image...";
                    oFd.Filter = "Images (*.jpg;*.png;*.bmp)|*.jpg;*.png;*.bmp";
                    if (oFd.ShowDialog() == DialogResult.OK)
                    {
                        Elem.Image = Image.FromFile(oFd.FileName);
                        Elem.Size = Elem.Image.Size;
                    }
                }

            //If we have descriptors, handle them too.
            Rectangle Bounds = Elem.Bounds;
            
            
            if (Elem.Definition["Descriptor"] != null)            
                AddDescriptor(Elem.Definition["Descriptor"], CIM, Elem);
            if (Elem.Definition["SecondaryDescriptor"] != null)
                AddDescriptor(Elem.Definition["SecondaryDescriptor"], CIM, Elem);
            AlignElementLabels(Elem, 'R', Elem.Bounds);

            if (Elem.Descriptor != null)
                Bounds = UnionRectangle(Bounds, Elem.Descriptor.Bounds);
            if (Elem.SecondaryDescriptor != null)
                Bounds = UnionRectangle(Bounds, Elem.SecondaryDescriptor.Bounds);

            //If our element is a node, or connected to a node, handle accordingly.
            if (Elem.ElemType == MM_OneLine_Element.enumElemTypes.Node)
            {
                if (!Nodes.Contains(Elem))
                    Nodes.Add(Elem);
                Elem.NodePaths = new Dictionary<MM_OneLine_Element, GraphicsPath>(Elem.Definition.ChildNodes.Count);
                foreach (XmlElement xElem in Elem.Definition.ChildNodes)
                    if (xElem.Name != "Descriptor" && xElem.Name != "SecondaryDescriptor")
                    {
                        XmlElement xNodeTarget=null;
                        if (xElem.HasAttribute("rdfID"))
                            xNodeTarget = OneLine.Document.SelectSingleNode("//*[@rdfID='" + xElem.Attributes["rdfID"].Value + "']") as XmlElement;
                        if (xNodeTarget == null)
                            xNodeTarget = OneLine.Document.SelectSingleNode("//*[@BaseElement.TEID='" + xElem.Attributes["TEID"].Value + "']") as XmlElement;
                        MM_OneLine_Element FoundElem;
                        if (Xml_Elements.TryGetValue(xNodeTarget, out FoundElem))
                            Elem.RebuildNodePath(FoundElem, xElem);                            
                    }
               
            }
            else if (Elem.ElemType == MM_OneLine_Element.enumElemTypes.PokePoint || Elem.ElemType == MM_OneLine_Element.enumElemTypes.PricingVector)
            {
            }
            else if (!Elem.CanSize && Elem.CIM != null )
            {
                //Look at our nodes connected to our element
                MM_OneLine_Element NodeElem;
                foreach (CIM_Element Node in Elem.CIM.Nodes)
                    if (Node.TypeName == "cim:ConnectivityNode" && CIM_Elements.TryGetValue(Node, out NodeElem))
                    {
                        MM_OneLine_Element.enumElemTypes ElemType = Elem.ElemType;
                        XmlElement NodeRef = OneLine.Document.CreateElement(ElemType.ToString());
                        NodeRef.Attributes.Append(OneLine.Document.CreateAttribute("TEID")).Value = Elem.CIM.TEID;
                        NodeRef.Attributes.Append(OneLine.Document.CreateAttribute("rdfID")).Value = Elem.CIM.rdfID.ToString();
                        NodeElem.Definition.AppendChild(NodeRef);
                        NodeElem.RebuildNodePath(Elem, NodeRef);
                        CIMListViewItem lvNode = lvElements.Items[NodeElem.CIM.rdfID.ToString()] as CIMListViewItem;
                        if (lvNode != null)
                            lvNode.Refresh(CIM_Elements, CIM_Descriptors, CIM_SecondaryDescriptors);
                    }
            }

            if (Elem.CIM != null)
            {
                CIMListViewItem lvElem = lvElements.Items[Elem.CIM.rdfID.ToString()] as CIMListViewItem;
                if (lvElem != null)
                    lvElem.Refresh(CIM_Elements, CIM_Descriptors, CIM_SecondaryDescriptors);
            }
            MoveElement(Elem, 0, 0, 0, 0);

            //If we're an end cap, update our orientation
            if (Elem.CIM != null && Elem.ElemType == MM_OneLine_Element.enumElemTypes.EndCap)
                Elem.Orientation = MM_OneLine_Element.enumOrientations.Unknown;


            return new Point(Bounds.Right + 5, Bounds.Top);
        }
        #endregion

        #region Button handling
        /// <summary>
        /// When the shading button is changed, refresh
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnShade_DropDownItemClicked(object sender, ToolStripItemClickedEventArgs e)
        {
            foreach (ToolStripMenuItem t in btnShade.DropDownItems)
                t.Checked = t.Equals(e.ClickedItem);
            pnlElements.Refresh();
        }

        #endregion

        /// <summary>
        /// Align all non display elements to the left-most coordinates
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnAlignLeft_Click(object sender, EventArgs e)
        {
            int LeftMost = int.MaxValue;
            pnlElements.SuspendLayout();
            foreach (MM_OneLine_Element Elem in SelectedElements)
                if (Elem.ElemType != MM_OneLine_Element.enumElemTypes.Descriptor && Elem.ElemType != MM_OneLine_Element.enumElemTypes.SecondaryDescriptor)
                    LeftMost = Math.Min(Elem.Left, LeftMost);

            Rectangle OutRect = Rectangle.Empty;
            foreach (MM_OneLine_Element Elem in SelectedElements)
                if (Elem.ElemType != MM_OneLine_Element.enumElemTypes.Descriptor && Elem.ElemType != MM_OneLine_Element.enumElemTypes.SecondaryDescriptor)
                    OutRect = UnionRectangle(OutRect, MoveElement(Elem, LeftMost - Elem.Left, 0, 0, 0));
            pnlElements.ResumeLayout();
            SelectionChanged(sender, e);
            InvalidatePanel(OutRect);
        }

        /// <summary>
        /// Center all selected elements
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnCenterVertically_Click(object sender, EventArgs e)
        {
            int Tally = 0, Count = 0;
            pnlElements.SuspendLayout();
            foreach (MM_OneLine_Element Elem in SelectedNonDescriptorElements)
            {
                Count++;
                Tally += Elem.Left + (Elem.Width / 2);
            }

            Rectangle OutRect = Rectangle.Empty;
            foreach (MM_OneLine_Element Elem in SelectedNonDescriptorElements)
                OutRect = UnionRectangle(OutRect, OutRect = MoveElement(Elem, ((Tally / Count) - (Elem.Width / 2)) - Elem.Left, 0, 0, 0));
            pnlElements.ResumeLayout();
            SelectionChanged(sender, e);
            InvalidatePanel(OutRect);
        }

        /// <summary>
        /// Center elements to the right
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnAlignRight_Click(object sender, EventArgs e)
        {
            int RightMost = int.MinValue;
            pnlElements.SuspendLayout();
            Rectangle OutRect = Rectangle.Empty;
            foreach (MM_OneLine_Element Elem in SelectedNonDescriptorElements)
                RightMost = Math.Max(Elem.Right, RightMost);
            foreach (MM_OneLine_Element Elem in SelectedNonDescriptorElements)
                OutRect = UnionRectangle(OutRect,MoveElement(Elem, RightMost - Elem.Width - Elem.Left, 0, 0, 0));
            pnlElements.ResumeLayout();
            InvalidatePanel(OutRect);
        }

        /// <summary>
        /// Align our elements to the top
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnAlignTop_Click(object sender, EventArgs e)
        {
            int TopMost = int.MaxValue;
            pnlElements.SuspendLayout();
            foreach (MM_OneLine_Element Elem in SelectedNonDescriptorElements)
                TopMost = Math.Min(Elem.Top, TopMost);
            Rectangle OutRect = Rectangle.Empty;
            foreach (MM_OneLine_Element Elem in SelectedNonDescriptorElements)
                OutRect = UnionRectangle(OutRect,MoveElement(Elem, 0, TopMost - Elem.Top, 0, 0));
            pnlElements.ResumeLayout();
            SelectionChanged(sender, e);
            InvalidatePanel(OutRect);
        }

        /// <summary>
        /// Center our elements horizontally
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnCenterHorizontally_Click(object sender, EventArgs e)
        {
            int Tally = 0, Count = 0;
            pnlElements.SuspendLayout();
            foreach (MM_OneLine_Element Elem in SelectedNonDescriptorElements)
            {
                Count++;
                Tally += Elem.Top + (Elem.Height / 2);
            }
            Rectangle OutRect = Rectangle.Empty;
            foreach (MM_OneLine_Element Elem in SelectedNonDescriptorElements)
                OutRect = UnionRectangle(OutRect,MoveElement(Elem, 0, (Tally / Count) - (Elem.Height / 2) - Elem.Top, 0, 0));
            pnlElements.ResumeLayout();
            InvalidatePanel(OutRect);
        }

        /// <summary>
        /// Align our elements to the bottom
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnAlignBottom_Click(object sender, EventArgs e)
        {
            int BottomMost = int.MinValue;
            this.SuspendLayout();
            foreach (MM_OneLine_Element Elem in SelectedNonDescriptorElements)
                BottomMost = Math.Max(Elem.Bottom, BottomMost);
            Rectangle OutRect = Rectangle.Empty;
            foreach (MM_OneLine_Element Elem in SelectedNonDescriptorElements)
                OutRect = UnionRectangle(OutRect, MoveElement(Elem, 0, BottomMost - Elem.Height - Elem.Top, 0, 0));
            pnlElements.ResumeLayout();
            InvalidatePanel(OutRect);
        }

        /// <summary>
        /// Align an element's labels
        /// </summary>
        /// <param name="ElemToAlign"></param>
        /// <param name="Alignment"></param>
        /// <param name="InRect"></param>
        private Rectangle AlignElementLabels(MM_OneLine_Element ElemToAlign, Char Alignment, Rectangle InRect)
        {
            int DTop = 0, STop = 0, DLeft = 0, SLeft = 0;
            //First, handle one dimension
            if (Alignment == 'L' || Alignment == 'R')
            {
                if (ElemToAlign.Descriptor != null && ElemToAlign.SecondaryDescriptor != null)
                {
                    STop = ElemToAlign.CenterY - ElemToAlign.SecondaryDescriptor.Height - 2 - ElemToAlign.SecondaryDescriptor.Top;
                    DTop = ElemToAlign.CenterY + ElemToAlign.Descriptor.Height + 2 - ElemToAlign.Descriptor.Top;
                }
                else if (ElemToAlign.Descriptor != null)
                    DTop = ElemToAlign.CenterY - ElemToAlign.Descriptor.CenterY;
                else if (ElemToAlign.SecondaryDescriptor != null)
                    STop = ElemToAlign.CenterY - ElemToAlign.SecondaryDescriptor.CenterY;
            }
            else
            {
                if (ElemToAlign.Descriptor != null && ElemToAlign.SecondaryDescriptor != null)
                {
                    DLeft = ElemToAlign.CenterX - ElemToAlign.Descriptor.Width - 2 - ElemToAlign.Descriptor.Left;
                    SLeft = ElemToAlign.CenterX + ElemToAlign.SecondaryDescriptor.Width + 2 - ElemToAlign.SecondaryDescriptor.Left;
                }
                else if (ElemToAlign.Descriptor != null)
                    DLeft = ElemToAlign.CenterX - ElemToAlign.Descriptor.CenterX;
                else if (ElemToAlign.SecondaryDescriptor != null)
                    SLeft = ElemToAlign.CenterX - ElemToAlign.SecondaryDescriptor.CenterX;
            }


            //Now, handle the remaining dimension
            if (Alignment == 'L')
            {
                if (ElemToAlign.Descriptor != null && ElemToAlign.SecondaryDescriptor != null)
                {
                    DLeft = Math.Min(ElemToAlign.Left - ElemToAlign.Descriptor.Width, ElemToAlign.Left - ElemToAlign.Descriptor.Width) - 4 - ElemToAlign.Descriptor.Left;
                    SLeft = Math.Min(ElemToAlign.Left - ElemToAlign.Descriptor.Width, ElemToAlign.Left - ElemToAlign.SecondaryDescriptor.Width) - 4 - ElemToAlign.SecondaryDescriptor.Left;
                }
                else if (ElemToAlign.Descriptor != null)
                    DLeft = ElemToAlign.Left - ElemToAlign.Descriptor.Width - 4 - ElemToAlign.Descriptor.Left;
                else if (ElemToAlign.SecondaryDescriptor != null)
                    SLeft = ElemToAlign.Left - ElemToAlign.SecondaryDescriptor.Width - 4 - ElemToAlign.SecondaryDescriptor.Left;
            }
            else if (Alignment == 'R')
            {
                if (ElemToAlign.Descriptor != null)
                    DLeft = ElemToAlign.Right + 4 - ElemToAlign.Descriptor.Left;
                if (ElemToAlign.SecondaryDescriptor != null)
                    SLeft = ElemToAlign.Right + 4 - ElemToAlign.SecondaryDescriptor.Left;
            }
            else if (Alignment == 'T')
            {
                if (ElemToAlign.Descriptor != null && ElemToAlign.SecondaryDescriptor != null)
                {
                    DTop = Math.Min(ElemToAlign.Top - ElemToAlign.Descriptor.Height, ElemToAlign.Top - ElemToAlign.SecondaryDescriptor.Height) - 4 - ElemToAlign.Descriptor.Top;
                    STop = Math.Min(ElemToAlign.Top - ElemToAlign.Descriptor.Height, ElemToAlign.Top - ElemToAlign.SecondaryDescriptor.Height) - 4 - ElemToAlign.SecondaryDescriptor.Top;
                }
                else if (ElemToAlign.Descriptor != null)
                    DTop = ElemToAlign.Top - ElemToAlign.Descriptor.Height - 4 - ElemToAlign.Descriptor.Top;
                else if (ElemToAlign.SecondaryDescriptor != null)
                    STop = ElemToAlign.Top - ElemToAlign.SecondaryDescriptor.Height - 4 - ElemToAlign.SecondaryDescriptor.Top;
            }
            else if (Alignment == 'B')
            {
                if (ElemToAlign.Descriptor != null)
                    DTop = ElemToAlign.Bottom + 4 - ElemToAlign.Descriptor.Top;
                if (ElemToAlign.SecondaryDescriptor != null)
                    STop = ElemToAlign.Bottom + 4 - ElemToAlign.SecondaryDescriptor.Top;
            }

            InRect = UnionRectangle(InRect, MoveElement(ElemToAlign, 0, 0, 0, 0));
            

            if (ElemToAlign.Descriptor != null)
                InRect = UnionRectangle(InRect, MoveElement(ElemToAlign.Descriptor, DLeft, DTop, 0, 0));
            if (ElemToAlign.SecondaryDescriptor != null)
                InRect = UnionRectangle(InRect, MoveElement(ElemToAlign.SecondaryDescriptor, SLeft, STop, 0, 0));
            return InRect;
        }

        /// <summary>
        /// Handle the alignment of labels
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void AlignLabels(object sender, EventArgs e)
        {
            //Start by determining the bounds that interest us
            int LeftMost = int.MaxValue, TopMost = int.MaxValue, RightMost = int.MinValue, BottomMost = int.MinValue;
            foreach (MM_OneLine_Element Elem in pnlElements.Controls)
                if (Elem.Selected && (Elem.Descriptor != null || Elem.SecondaryDescriptor != null))
                {
                    LeftMost = Math.Min(LeftMost, Elem.Left);
                    TopMost = Math.Min(TopMost, Elem.Top);
                    RightMost = Math.Max(RightMost, Elem.Right);
                    BottomMost = Math.Max(BottomMost, Elem.Bottom);
                }

            //Now, align all elements            
            Char[] Style = ((sender as ToolStripItem).Tag as string).ToCharArray();

            //If most of the elements are left or right-aligned, handle accordingly.
            Rectangle OutRect = Rectangle.Empty;
            if (Style[1] == 'L' || Style[1] == 'R')
                foreach (MM_OneLine_Element Elem in SelectedElements)
                    if (Elem.Top == TopMost)
                        OutRect = UnionRectangle(OutRect,AlignElementLabels(Elem, Style[0], OutRect));
                    else if (Elem.Bottom == BottomMost)
                        OutRect = UnionRectangle(OutRect,AlignElementLabels(Elem, Style[2], OutRect));
                    else
                        OutRect = UnionRectangle(OutRect,AlignElementLabels(Elem, Style[1], OutRect));
            else
                foreach (MM_OneLine_Element Elem in SelectedElements)
                    if (Elem.Left == LeftMost)
                        OutRect = UnionRectangle(OutRect,AlignElementLabels(Elem, Style[0], OutRect));
                    else if (Elem.Right == RightMost)
                        OutRect = UnionRectangle(OutRect,AlignElementLabels(Elem, Style[2], OutRect));
                    else
                        OutRect = UnionRectangle(OutRect,AlignElementLabels(Elem, Style[1], OutRect));
            pnlElements.Refresh();
        }

        /// <summary>
        /// Perform the validation on our one-line
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnValidate_Click(object sender, EventArgs e)
        {
            int Warnings = 0, Errors = 0;
            String ValidationResults = MM_OneLine_Validation.ValidateTopology(this.OneLine.Document.DocumentElement, CIM, new MM_Oneline_Export(this.OneLine, CIM), out Errors, out Warnings);
            ApplyValidationResults(ValidationResults);
            MessageBox.Show(String.Format("Validation of {0} {1}: {2:#,##0} errors, {3:#,##0} warnings:\n\n{4}", BaseElement.ElemType, BaseElement.Name, Errors, Warnings, ValidationResults), Application.ProductName, MessageBoxButtons.OK, (Errors > 0 ? MessageBoxIcon.Error : Warnings > 0 ? MessageBoxIcon.Warning : MessageBoxIcon.Information));
        }

        /// <summary>
        /// Handle the selection changes
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ctlOneLineEditor_SelectionChanged(object sender, EventArgs e)
        {
            pgOneLine.SelectedObjects = SelectedElements.ToArray();
        }

        /// <summary>
        /// When a property changes, update all of the node points to those elements
        /// </summary>
        /// <param name="s"></param>
        /// <param name="e"></param>
        private void pgOneLine_PropertyValueChanged(object s, PropertyValueChangedEventArgs e)
        {
            Rectangle OutRect = Rectangle.Empty;
            foreach (MM_OneLine_Element Elem in pgOneLine.SelectedObjects)
            {
                OutRect = UnionRectangle(OutRect, MoveElement(Elem, 0, 0, 0, 0));
                Elem.Refresh();
            }
            InvalidatePanel(OutRect);
        }

        /// <summary>
        /// Clear our one-line, so nothing is shown
        /// </summary>
        public void ClearOneLine()
        {
            BaseElement = null;
            OneLine = null;
            Assignment = null;
            List<MM_OneLine_Element> ElemsToDispose = new List<MM_OneLine_Element>();
            foreach (MM_OneLine_Element Elem in pnlElements.Controls)
                ElemsToDispose.Add(Elem);
            pnlElements.Controls.Clear();
            foreach (MM_OneLine_Element Elem in ElemsToDispose)
                Elem.Dispose();
            lvElements.Items.Clear();
            this.Refresh();

        }

        /// <summary>
        /// Handle the rotation of an element
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnRotate_Click(object sender, EventArgs e)
        {
            Rectangle OutRect = Rectangle.Empty;
            foreach (MM_OneLine_Element Elem in SelectedElements)
            {
                //First, determine the current orientation
                
                MM_OneLine_Element.enumOrientations Orientation = Elem.Orientation;
                MM_OneLine_Element.enumOrientations[] AllowedOrientations = MM_OneLine_Element.AllowedOrientations(Elem.ElemType, BaseElement.TypeName == "cim:Substation" && Elem.CIM.TypeName != "cim:SeriesCompensator");
                
                //If we're a node w/ unknown orientation, update accordingly
                if (Elem.ElemType == MM_OneLine_Element.enumElemTypes.Node && Elem.Orientation == MM_OneLine_Element.enumOrientations.Unknown && Elem.Width != Elem.Height)
                    if (Elem.Width > Elem.Height)
                        Elem.Orientation = MM_OneLine_Element.enumOrientations.Horizontal;
                    else
                        Elem.Orientation = MM_OneLine_Element.enumOrientations.Vertical;
                else if (Elem.ElemType == MM_OneLine_Element.enumElemTypes.Transformer && (Elem.Orientation == MM_OneLine_Element.enumOrientations.Up || Elem.Orientation == MM_OneLine_Element.enumOrientations.Down))
                    Elem.Orientation = MM_OneLine_Element.enumOrientations.Vertical;
                else if (Elem.ElemType == MM_OneLine_Element.enumElemTypes.Transformer && (Elem.Orientation == MM_OneLine_Element.enumOrientations.Left || Elem.Orientation == MM_OneLine_Element.enumOrientations.Right))
                    Elem.Orientation = MM_OneLine_Element.enumOrientations.Vertical;
                else
                {
                    MM_OneLine_Element.enumOrientations NewOrientation = AllowedOrientations[(Math.Max(0, Array.IndexOf(AllowedOrientations, Orientation)) + 1) % AllowedOrientations.Length];
                    if (NewOrientation == MM_OneLine_Element.enumOrientations.Unknown)
                        NewOrientation = AllowedOrientations[(Math.Max(0, Array.IndexOf(AllowedOrientations, NewOrientation)) + 1) % AllowedOrientations.Length];
                    Elem.Orientation = NewOrientation;
                }
                OutRect = UnionRectangle(OutRect, MoveElement(Elem, 0, 0, 0, 0));
                Elem.Refresh();
            }
            InvalidatePanel(OutRect);
        }

        /// <summary>
        /// Handle the flip of an element
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnFlipTransformerWindings_Click(object sender, EventArgs e)
        {
            Rectangle OutRect = Rectangle.Empty;
            foreach (MM_OneLine_Element Elem in SelectedElements)
                if (Elem.ElemType == MM_OneLine_Element.enumElemTypes.Transformer)
                {
                    /*
                    bool OldPhaseShifter = Elem.Windings[0].IsPhaseShifter;
                    bool OldPrimary = Elem.Windings[0].IsPrimary;
                    bool OldVisible = Elem.Windings[0].Visible;                    
                    Rectangle OldBounds = Elem.Windings[0].Bounds;
                    
                    Elem.Windings[0].Bounds = Elem.Windings[1].Bounds;
                    Elem.Windings[0].Visible = Elem.Windings[1].Visible;
                    Elem.Windings[0].IsPrimary = Elem.Windings[1].IsPrimary;
                    Elem.Windings[0].IsPhaseShifter= Elem.Windings[1].IsPhaseShifter;

                    Elem.Windings[1].Bounds = OldBounds;
                    Elem.Windings[1].Visible = OldVisible;
                    Elem.Windings[1].IsPrimary = OldPrimary;
                    Elem.Windings[1].IsPhaseShifter = OldPhaseShifter;
                    */
                    //Determine our orientation
                    Elem.Windings = new MM_OneLine_TransformerWinding[] { Elem.Windings[1], Elem.Windings[0] };
                    if (Elem.Orientation == MM_OneLine_Element.enumOrientations.Vertical || Elem.Orientation == MM_OneLine_Element.enumOrientations.Up || Elem.Orientation == MM_OneLine_Element.enumOrientations.Down)
                    {
                        Elem.Orientation = MM_OneLine_Element.enumOrientations.Vertical;
                        Elem.Windings[0].Orientation = MM_OneLine_Element.enumOrientations.Up;
                        Elem.Windings[0].Bounds = new Rectangle(0, 0, Elem.Width, Elem.Height / 2);
                        Elem.Windings[1].Orientation = MM_OneLine_Element.enumOrientations.Down;
                        Elem.Windings[1].Bounds = new Rectangle(0, Elem.Height/2, Elem.Width, Elem.Height / 2);
                    }
                    else if (Elem.Orientation == MM_OneLine_Element.enumOrientations.Horizontal || Elem.Orientation == MM_OneLine_Element.enumOrientations.Left || Elem.Orientation == MM_OneLine_Element.enumOrientations.Right)
                    {
                        Elem.Orientation = MM_OneLine_Element.enumOrientations.Horizontal;
                        Elem.Windings[0].Orientation = MM_OneLine_Element.enumOrientations.Right;
                        Elem.Windings[0].Bounds = new Rectangle(0, 0, Elem.Width/2, Elem.Height);
                        Elem.Windings[1].Orientation = MM_OneLine_Element.enumOrientations.Left;
                        Elem.Windings[1].Bounds = new Rectangle(Elem.Width/2, 0, Elem.Width / 2, Elem.Height);

                    }                    
                    Elem.Refresh();
                    MoveElement(Elem, 0, 0,0,0);
                    /*if (Elem.Orientation == MM_OneLine_Element.enumOrientations.Up)
                        Elem.Orientation = MM_OneLine_Element.enumOrientations.Down;
                    else if (Elem.Orientation == MM_OneLine_Element.enumOrientations.Down)
                        Elem.Orientation = MM_OneLine_Element.enumOrientations.Up;
                    else if (Elem.Orientation == MM_OneLine_Element.enumOrientations.Left)
                        Elem.Orientation = MM_OneLine_Element.enumOrientations.Right;
                    else if (Elem.Orientation == MM_OneLine_Element.enumOrientations.Right)
                        Elem.Orientation = MM_OneLine_Element.enumOrientations.Left;*/

                    //Repair the export points
                    if (Elem.Descriptor != null)
                    {
                        Elem.Descriptor.ExportValues =MM_OneLine_Element.BuildExportPoints(Elem.CIM, BaseElement, Elem.Descriptor.ElemType == MM_OneLine_Element.enumElemTypes.SecondaryDescriptor, Elem.Descriptor.Definition);
                        OutRect = UnionRectangle(MoveElement(Elem.Descriptor, 0, 0, 0, 0), OutRect);
                    }
                    
                    
                }
            InvalidatePanel(OutRect);
        }

        /// <summary>
        /// Update our datagrid view as elements change
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void tvXml_AfterSelect(object sender, TreeViewEventArgs e)
        {            
            //Now, show all attributes for our element   
            
            dgvAttribute.Rows.Clear();
            if (e.Node != null)
            {
                XmlElement NodeElem = e.Node.Tag as XmlElement;
                foreach (XmlAttribute xAttr in NodeElem.Attributes)
                    dgvAttribute.Rows.Add(xAttr.Name, xAttr.Value);

                dgvAttribute.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCells);
                dgvAttribute.Sort(dgvAttribute.Columns[0], ListSortDirection.Ascending);

                //Select our element
                MM_OneLine_Element FoundElem = null;
                Xml_Elements.TryGetValue(NodeElem, out FoundElem);
                foreach (MM_OneLine_Element Elem in pnlElements.Controls)
                    if (Elem.Equals(FoundElem))
                    {
                        Elem.Selected = true;
                        pnlElements.ScrollControlIntoView(Elem);
                    }
                    else
                        Elem.Selected = false;
               // pnlElements.Refresh();
            }
        }

        /// <summary>
        /// Highlight our element based on the selected item
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lvElements_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (pnlElements.Tag is bool && (bool)pnlElements.Tag)
                return;
            //Set up our selected elements based on what elements are selected
            List<MM_OneLine_Element> CurSelection = SelectedElements;
            MM_OneLine_Element FoundElem;
            foreach (CIMListViewItem lvI in lvElements.SelectedItems)
                if (CIM_Elements.TryGetValue(lvI.AssociatedElement, out FoundElem))
            {                
                CurSelection.Remove(FoundElem);
                FoundElem.Selected=true;
            }

            foreach (MM_OneLine_Element Elem in CurSelection)
                Elem.Selected = false;

            RefreshElements(CurSelection.ToArray());


            /*
            if (lvElements.SelectedItems.Count != 1)
                return;
            CIM_Element cElem = (lvElements.SelectedItems[0] as CIMListViewItem).AssociatedElement;
            MM_OneLine_Element Elem;
            if (!CIM_Elements.TryGetValue(cElem, out Elem))
                return;
            foreach (MM_OneLine_Element Elem2 in pnlElements.Controls)
                Elem2.Selected = Elem.Equals(Elem2);
            pnlElements.ScrollControlIntoView(Elem);
            TreeNode SelNode;
            if (Xml_Nodes.TryGetValue(Elem, out SelNode))
                tvXml.SelectedNode = SelNode;
            pnlElements.Refresh();*/
        }

        /// <summary>
        /// Turn on/off arrows on selected elements
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnDrawArrow_Click(object sender, EventArgs e)
        {
            foreach (MM_OneLine_Element Elem in SelectedElements)
                Elem.DescriptorArrow ^= true;
            pnlElements.Refresh();
        }

        /// <summary>
        /// Set up busbar sections as needed
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnBusbar_Click(object sender, EventArgs e)
        {
            //Go through each node, and determine whether it has a busbar section, and adjust accordingly.
            Rectangle OutRect = Rectangle.Empty;
            foreach (MM_OneLine_Element Node in Nodes)
            {
                bool HasBusbar = Node.CIM.ParseSingle("cim:Terminal>cim:BusbarSection", null) != null;
                bool HasRN = Node.CIM.ParseSingle("etx:ElectricalBus<etx:ResourceNode", null) != null;

                //Size our node accordingly
                if (!HasBusbar && Node.Size != new Size(8, 8))
                    Node.Size = new Size(8, 8);
                else if (HasBusbar && Node.Width <= 8 && Node.Height <= 8)
                {
                    //Try and guess at the orientation of our node based on to its interconnected elements
                    int Horiz = 0, Vert = 0;

                    //Look at the interconnected elements and view their differentials
                    if (Node.NodePaths != null)
                    foreach (KeyValuePair<MM_OneLine_Element, GraphicsPath> kvp in Node.NodePaths)
                    {
                        PointF OtherPoint;
                        if (Node.Bounds.Contains(Point.Round(kvp.Value.PathPoints[0])))
                            OtherPoint = kvp.Value.PathPoints[1];
                        else //if (Node.Bounds.Contains(Point.Round(kvp.Value.PathPoints[0])))
                            OtherPoint = kvp.Value.PathPoints[kvp.Value.PointCount - 2];

                        if (Math.Abs(OtherPoint.X - Node.CenterX) > Math.Abs(OtherPoint.Y - Node.CenterY))
                            Horiz++;
                        else
                            Vert++;
                    }

                    if (Horiz < Vert)
                    {
                        Node.Orientation = MM_OneLine_Element.enumOrientations.Vertical;
                        OutRect = UnionRectangle(OutRect, MoveElement(Node, 0, 0, 8 - Node.Width, 50 - Node.Height));                        
                    }
                    else
                    {
                        Node.Orientation = MM_OneLine_Element.enumOrientations.Horizontal;
                        OutRect = UnionRectangle(OutRect, MoveElement(Node, 0, 0, 50 - Node.Width, 8 - Node.Height));                                                
                    }
                }

                //Now handle descriptors
                if (!HasBusbar && Node.Descriptor != null)
                {
                    OutRect = UnionRectangle(OutRect, Node.Descriptor.Bounds);
                    Node.Descriptor.Definition.ParentNode.RemoveChild(Node.Descriptor.Definition);
                    pnlElements.Controls.Remove(Node.Descriptor);
                    CIM_Descriptors.Remove(Node.CIM);
                    Node.Descriptor = null;
                    (lvElements.Items[Node.CIM.rdfID.ToString()] as CIMListViewItem).Refresh(CIM_Elements, CIM_Descriptors, CIM_SecondaryDescriptors);
                }
                else if (HasBusbar && Node.Descriptor == null)
                {
                    //Create our new descriptor element
                    XmlElement Desc = Node.Definition.OwnerDocument.CreateElement("Descriptor");
                    Node.Definition.AppendChild(Desc);
                    Node.Descriptor = new MM_OneLine_Element(Desc, CIM, BaseElement);
                    Node.Descriptor.ParentElement = Node;
                    Node.Descriptor.MouseDown += Elem_MouseDown;
                    Node.Descriptor.MouseUp += Elem_MouseUp;
                    Node.Descriptor.MouseDoubleClick += Elem_MouseDoubleClick;
                    Node.Descriptor.MouseMove += Elem_MouseMove;
                    Node.Descriptor.ExportValues = MM_OneLine_Element.BuildExportPointsWithLocations(Node.Definition, MM_OneLine_Element.BuildExportPoints(Node.CIM, BaseElement, Node.Descriptor.ElemType == MM_OneLine_Element.enumElemTypes.SecondaryDescriptor,Desc), Node.Bounds, Node.Descriptor.Bounds);
                    pnlElements.Controls.Add(Node.Descriptor);
                    CIM_Descriptors.Add(Node.CIM, Node.Descriptor);
                    (lvElements.Items[Node.CIM.rdfID.ToString()] as CIMListViewItem).Refresh(CIM_Elements, CIM_Descriptors, CIM_SecondaryDescriptors);
                    if (Node.Orientation == MM_OneLine_Element.enumOrientations.Vertical)
                        OutRect = UnionRectangle(OutRect,AlignElementLabels(Node, 'R', OutRect));
                    else
                        OutRect = UnionRectangle(OutRect,AlignElementLabels(Node, 'B', OutRect));

                }

                //Check for resource nodes
                if (HasRN && Node.SecondaryDescriptor == null)
                {
                    XmlElement Desc = Node.Definition.OwnerDocument.CreateElement("SecondaryDescriptor");
                    Node.Definition.AppendChild(Desc);
                    Node.SecondaryDescriptor = new MM_OneLine_Element(Desc, CIM, BaseElement);
                    Node.SecondaryDescriptor.Location = new Point(Node.Right + 4, Node.Top);
                    Node.SecondaryDescriptor.ParentElement = Node;
                    Node.SecondaryDescriptor.MouseDown += Elem_MouseDown;
                    Node.SecondaryDescriptor.MouseUp += Elem_MouseUp;
                    Node.SecondaryDescriptor.MouseDoubleClick += Elem_MouseDoubleClick;
                    Node.SecondaryDescriptor.MouseMove += Elem_MouseMove;

                    Node.SecondaryDescriptor.ExportValues = MM_OneLine_Element.BuildExportPointsWithLocations(Node.Definition, MM_OneLine_Element.BuildExportPoints(Node.CIM, BaseElement, Node.SecondaryDescriptor.ElemType == MM_OneLine_Element.enumElemTypes.SecondaryDescriptor,Desc), Node.Bounds, Node.SecondaryDescriptor.Bounds);
                    pnlElements.Controls.Add(Node.SecondaryDescriptor);
                    CIM_SecondaryDescriptors.Add(Node.CIM, Node.SecondaryDescriptor);
                    (lvElements.Items[Node.CIM.rdfID.ToString()] as CIMListViewItem).Refresh(CIM_Elements, CIM_Descriptors, CIM_SecondaryDescriptors);
                    if (Node.Orientation == MM_OneLine_Element.enumOrientations.Horizontal)
                        OutRect = UnionRectangle(OutRect,AlignElementLabels(Node, 'B',OutRect));
                    else
                        OutRect = UnionRectangle(OutRect,AlignElementLabels(Node, 'R',OutRect));
                }
            }
            InvalidatePanel(OutRect);
        }

        /// <summary>
        /// Save our one-line
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ExportComponent(object sender, EventArgs e)
        {
            //First, run our validation
            int Warnings = 0, Errors = 0;
            String ValidationResults;
            MM_Oneline_Export OLExport=null;
            try
            {
                OLExport = new MM_Oneline_Export(OneLine, BaseElement.CIM);
                ValidationResults = MM_OneLine_Validation.ValidateTopology(this.OneLine.Document.DocumentElement, CIM, OLExport,out Errors, out Warnings);                                            
            }
            catch (Exception ex)
            {
                Errors = 1000;
                ValidationResults = ex.Message + "\n" + ex.StackTrace;                
            }

            try
            {
                ApplyValidationResults(ValidationResults);
            }
            catch (Exception)
            { }


            ToolStripButton Sender = sender as ToolStripButton;
            if (Warnings + Errors > 0)
                if (MessageBox.Show(String.Format("Validation of {0} {1}: {2:#,##0} errors, {3:#,##0} warnings. Would you like to " + Sender.Text + " anyway?\n\n{4}", BaseElement.ElemType, BaseElement.Name, Errors, Warnings, ValidationResults), Application.ProductName, MessageBoxButtons.YesNo, (Errors > 0 ? MessageBoxIcon.Error : Warnings > 0 ? MessageBoxIcon.Warning : MessageBoxIcon.Information), MessageBoxDefaultButton.Button2) != DialogResult.Yes)
                    return;


            //Now, process accordingly
            MM_Database_Assignment LastAssignment = Repository.Db.GetLastAssignment(OneLine);
            if (Sender == btnSaveOneLine)
            {
                if (OneLine.ID == -1)
                {
                    if (MessageBox.Show("This one-line isn't associated with a model in the database. Would you like to save it anyway?", Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                    {
                        OneLine.OneLineType = MM_Database_OneLine.OneLineTypeEnum.Substation;
                        using (DbCommand dCmd = Repository.Db.CreateCommand("SELECT SEQ_MM_DATABASE_ONELINE.NextVal FROM DUAL"))
                            OneLine.ID = Convert.ToInt32(dCmd.ExecuteScalar());
                        OneLine.WriteRdb(Repository);


                        //Create our assignment
                        using (OracleCommand oCmd = new OracleCommand("INSERT INTO MM_DATABASE_ASSIGNMENT(\"ONELINE\", \"MODEL\", ASSIGNEDTO, \"ASSIGNEDON\",\"NOTES\", \"ID\",\"ERRORS\",\"WARNINGS\", \"ONELINETYPE\", \"ONELINENAME\", \"COMPLETEDON\", \"COMPLETEDBY\") VALUES (:6, :7, :8, :9, :10, SEQ_MM_DATABASE_ASSIGNMENT.NEXTVAL,:11,:12, :13, :14, :15, :16)", Repository.Db.Database as OracleConnection))
                        {
                            oCmd.Prepare();
                            oCmd.Parameters.Add(Repository.Db.CreateParameter(":6", OneLine.ID));
                            oCmd.Parameters.Add(Repository.Db.CreateParameter(":7", -1));
                            oCmd.Parameters.Add(Repository.Db.CreateParameter(":8", Repository.CurrentUser.ID));
                            oCmd.Parameters.Add(Repository.Db.CreateParameter(":9", DateTime.Now));
                            oCmd.Parameters.Add(Repository.Db.CreateParameter(":10", "One-line created against model " + CIM.Name + " by " + Repository.CurrentUser.FullName + " on " + DateTime.Now.ToString()));
                            oCmd.Parameters.Add(Repository.Db.CreateParameter(":11", OLExport.ElementsByRdfID.Count));
                            oCmd.Parameters.Add(Repository.Db.CreateParameter(":12", 0));
                            oCmd.Parameters.Add(Repository.Db.CreateParameter(":13", 0));
                            oCmd.Parameters.Add(Repository.Db.CreateParameter(":14", MM_Type_Converter.TitleCase(BaseElement.Name)));
                            oCmd.Parameters.Add(Repository.Db.CreateParameter(":15", DateTime.Now));
                            oCmd.Parameters.Add(Repository.Db.CreateParameter(":16", Repository.CurrentUser.ID));
                            int RowsInserted = oCmd.ExecuteNonQuery();
                            if (RowsInserted != 1)
                                MessageBox.Show("Warning! Error inserting the assignment for element " + BaseElement.ToString(), Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Warning);
                        }
                    }
                    return;
                }

                //Backup our one-line first
                Repository.Db.BackupOneLine(OneLine, Repository.CurrentUser);
               
                //First, save our one-line
                OneLine.UpdateRdb(Repository);

                //Write out our image
                using (Image OutImage = MM_Image_Exporter.OneLineImage(OneLine, BaseElement.CIM, new MM_Oneline_Export(OneLine, CIM), LastAssignment))
                using (DbCommand dCmd2 = Repository.Db.CreateCommand("UPDATE MM_DATABASE_ONELINE SET IMAGE=:0 WHERE ID=:1", OutImage, OneLine.ID))
                    if (dCmd2.ExecuteNonQuery() != 1)
                        MessageBox.Show("Unable to save one-line image!", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Warning);
                

                //Now, mark our assignment and update
                if (Assignment != null)
                {
                    Assignment.CompletedOn = DateTime.Now;
                    Assignment.CompletedBy = Repository.CurrentUser;
                    Assignment.UpdateRdb(Repository);
                }

                if (OneLineSaved != null)
                    OneLineSaved(this, EventArgs.Empty);
            }
            else
                using (SaveFileDialog sFd = new SaveFileDialog())
                {
                    sFd.Title = Sender.Text + "...";
                    if (Sender == btnExportDDL)
                    {

                        sFd.Filter = "Web FG one-line, image, and compiled displays|*_dset.ddl";
                        sFd.FileName = BaseElement.Name + ".ddl";
                    }
                    else if (Sender == btnExportImage)
                    {
                        sFd.Filter = "PNG Image (*.png)|*.png|JPEG Image (*.jpg)|*.jpg|Bitmap Image (*.bmp)|*.bmp|GIF Image (*.gif)|*.gif";
                        sFd.FileName = BaseElement.Name + ".png";
                    }
                    else if (sender == btnSaveXml)
                    {
                        sFd.Filter = "Macomber Map One-Line (*.MM_OneLine)|*.MM_OneLine";
                        sFd.FileName = BaseElement.Name + ".MM_OneLine";
                    }
                    
                    if (sFd.ShowDialog() == DialogResult.OK)
                        if (sFd.FileName.EndsWith(".ddl", StringComparison.CurrentCultureIgnoreCase))
                        {
                            //Determine our target directory, and ensure that our structure is in place
                            XmlElement xExport = Repository.xConfig.SelectSingleNode("Configuration/OneLineExport") as XmlElement;
                            Dictionary<String, Object> OutDic = new Dictionary<string, object>();
                            OutDic.Add("Model", BaseElement.CIM);
                            OutDic.Add("PriorModel", BaseElement.CIM.PriorModel);
                            CIM_OneLineValidator OLValidator = new CIM_OneLineValidator(xExport, OutDic, Repository);
                            String ImageDirectory = OLValidator.ProcessString(xExport.Attributes["ImageDirectory"].Value);
                            String EMSDirectory = OLValidator.ProcessString(xExport.Attributes["EMSDirectory"].Value);
                            String MMXmlDirectory = null;
                            String ExpDirectory = Path.GetDirectoryName(sFd.FileName);
                            Exception CompilationException = MM_DDL_Exporter.WriteOneLine(OneLine, BaseElement.CIM, ExpDirectory, ExpDirectory, ImageDirectory, EMSDirectory, OneLine.Names[0], "", MM_Image_Exporter.OneLineImage(OneLine, BaseElement.CIM, OLExport, LastAssignment), Repository, true, true, true, true, MMXmlDirectory, null, LastAssignment);
                            if (CompilationException != null)
                                MessageBox.Show("One-line compilation failed:" + MM_System_Interface.ConvertError(CompilationException), Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Stop);                                                        
                        }
                        else if (sFd.FileName.EndsWith(".MM_OneLine", StringComparison.CurrentCultureIgnoreCase) || sFd.FileName.EndsWith(".xml", StringComparison.CurrentCultureIgnoreCase))
                            OneLine.Document.Save(sFd.FileName);
                        else if (sFd.FileName.EndsWith(".jpg", StringComparison.CurrentCultureIgnoreCase))
                            MM_Image_Exporter.OneLineImage(OneLine, BaseElement.CIM, OLExport, LastAssignment).Save(sFd.FileName, ImageFormat.Jpeg);
                        else if (sFd.FileName.EndsWith(".bmp", StringComparison.CurrentCultureIgnoreCase))
                            MM_Image_Exporter.OneLineImage(OneLine, BaseElement.CIM, OLExport, LastAssignment).Save(sFd.FileName, ImageFormat.Bmp);
                        else if (sFd.FileName.EndsWith(".png", StringComparison.CurrentCultureIgnoreCase))
                            MM_Image_Exporter.OneLineImage(OneLine, BaseElement.CIM, OLExport, LastAssignment).Save(sFd.FileName, ImageFormat.Png);
                        else if (sFd.FileName.EndsWith(".gif", StringComparison.CurrentCultureIgnoreCase))
                            MM_Image_Exporter.OneLineImage(OneLine, BaseElement.CIM, OLExport, LastAssignment).Save(sFd.FileName, ImageFormat.Gif);
                        else
                            MessageBox.Show("Don't know how to export file of type " + sFd.FileName, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Stop);
                }
        }

        /// <summary>
        /// Handle our drag/drop events
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void HandleListViewItemDragging(object sender, ItemDragEventArgs e)
        {
            //Build our list of selected toolbox items
            List<MM_OneLine_Element> OutElems = new List<MM_OneLine_Element>();
            foreach (ListViewItem lvI in (sender as ListView).SelectedItems)
                if (lvI is CIMListViewItem)
                {
                    MM_OneLine_Element Elem;
                    CIM_Element cElem = (lvI as CIMListViewItem).AssociatedElement;
                    if (CIM_Elements.TryGetValue(cElem, out Elem))
                        OutElems.Add(Elem);
                    else
                    {
                        MM_OneLine_Element OutElem;
                        try
                        {
                            OutElem = new MM_OneLine_Element(cElem, BaseElement, OneLine.Document);
                        }
                        catch (Exception ex)
                        {
                            Clipboard.SetText(ex.ToString());
                            MessageBox.Show(this, "Error creating new element: " + ex.ToString(), Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Stop);
                            return;
                        }
                        CIM_Element XF;
                        //If we're a node, match up with our other elements
                        if (cElem.TypeName == "cim:ConnectivityNode")
                            foreach (CIM_Element ConnectedElement in cElem.Elements)
                                if (CIM_Elements.ContainsKey(ConnectedElement))
                                {
                                    XmlElement NewChild = OneLine.Document.CreateElement(ConnectedElement.ElemType);
                                    NewChild.Attributes.Append(OneLine.Document.CreateAttribute("rdfID")).Value = ConnectedElement.rdfID.ToString();
                                    NewChild.Attributes.Append(OneLine.Document.CreateAttribute("TEID")).Value = ConnectedElement.TEID;
                                    OutElem.Definition.AppendChild(NewChild);
                                }
                                else if (ConnectedElement.TypeName == "cim:TransformerWinding" && CIM_Elements.ContainsKey(XF = ConnectedElement.ParseSingle("cim:PowerTransformer", null)))
                                {
                                    XmlElement NewChild = OneLine.Document.CreateElement(XF.ElemType);
                                    NewChild.Attributes.Append(OneLine.Document.CreateAttribute("rdfID")).Value = XF.rdfID.ToString();
                                    NewChild.Attributes.Append(OneLine.Document.CreateAttribute("TEID")).Value = XF.TEID;
                                    OutElem.Definition.AppendChild(NewChild);
                                }

                        OutElems.Add(OutElem);
                    }
                }

                else
                {
                    XmlElement NewElem = OneLine.Document.CreateElement(lvI.Text);
                    OutElems.Add(new MM_OneLine_Element(NewElem, CIM, BaseElement));
                    OneLine.Document.DocumentElement["Unlinked_Elements"].AppendChild(NewElem);
                }
            if (OutElems.Count > 0)
                lvToolbox.DoDragDrop(OutElems.ToArray(), DragDropEffects.All);
        }

        //Shift elements so that there are no collisions
        private void btnShiftElements_Click(object sender, EventArgs e)
        {
            //First, build our list of all elements
            List<MM_OneLine_Element> ElemsToProcess = new List<MM_OneLine_Element>();
            Dictionary<MM_OneLine_Element, Rectangle> ElemBounds = new Dictionary<MM_OneLine_Element, Rectangle>();
            foreach (MM_OneLine_Element Elem in pnlElements.Controls)
                if (Elem.CIM != null)
                {
                    ElemsToProcess.Add(Elem);
                    ElemBounds.Add(Elem, Elem.OuterBounds);
                }

            //First, perform our hit testing against the title bar
            PerformHitTest(new Rectangle(0, 0, pnlElements.Width, 50), null, ElemsToProcess, ElemBounds);

            //Now, perform hit testing against every element
            bool HadChange = false;
            do
            {
                HadChange = false;
                foreach (MM_OneLine_Element Elem in ElemsToProcess)
                    if (Elem.ElemType != MM_OneLine_Element.enumElemTypes.Label && Elem.ElemType != MM_OneLine_Element.enumElemTypes.Image && Elem.ElemType != MM_OneLine_Element.enumElemTypes.Circle && Elem.ElemType != MM_OneLine_Element.enumElemTypes.Rectangle && Elem.ElemType != MM_OneLine_Element.enumElemTypes.ArrowLine)
                        HadChange = HadChange || PerformHitTest(ElemBounds[Elem], Elem, ElemsToProcess, ElemBounds);
            }
            while (HadChange);     

            //Update the positioning of each element
            Rectangle OutRect = Rectangle.Empty;
            foreach (KeyValuePair<MM_OneLine_Element, Rectangle> kvp in ElemBounds)
                OutRect = UnionRectangle(OutRect, MoveElement(kvp.Key, kvp.Value.X - kvp.Key.OuterBounds.X, kvp.Value.Y - kvp.Key.OuterBounds.Y, 0, 0));
           
        }

        /// <summary>
        /// Perform a hit test from a rectangle to all elements, and move as needed
        /// </summary>
        /// <param name="Rect"></param>
        /// <param name="Elem"></param>
        /// <param name="ElemsToProcess"></param>
        /// <param name="ElemBounds"></param>
        private bool PerformHitTest(Rectangle Rect, MM_OneLine_Element Elem, List<MM_OneLine_Element> ElemsToProcess, Dictionary<MM_OneLine_Element, Rectangle> ElemBounds)
        {
            //Go through each element, and find ones that intersect on the X axis
            bool MadeChange = false;
            foreach (MM_OneLine_Element ElemToCheck in ElemsToProcess)
                if (ElemToCheck != Elem)
                    if (ElemToCheck.ElemType != MM_OneLine_Element.enumElemTypes.Label && ElemToCheck.ElemType != MM_OneLine_Element.enumElemTypes.Image && ElemToCheck.ElemType != MM_OneLine_Element.enumElemTypes.Circle && ElemToCheck.ElemType != MM_OneLine_Element.enumElemTypes.Rectangle && ElemToCheck.ElemType != MM_OneLine_Element.enumElemTypes.ArrowLine)
                    {
                        Rectangle CompareRect = ElemBounds[ElemToCheck];


                        //If our element is horizontal, shift downward in case of a collision
                        MM_OneLine_Element.enumOrientations Orientation = Elem == null ? MM_OneLine_Element.enumOrientations.Horizontal : Elem.ParentElement != null ? Elem.ParentElement.Orientation : Elem.Orientation;
                        if (Orientation == MM_OneLine_Element.enumOrientations.Left || Orientation == MM_OneLine_Element.enumOrientations.Right)
                            Orientation = MM_OneLine_Element.enumOrientations.Horizontal;
                        else if (Orientation == MM_OneLine_Element.enumOrientations.Up || Orientation == MM_OneLine_Element.enumOrientations.Down)
                            Orientation = MM_OneLine_Element.enumOrientations.Vertical;

                        if (Orientation == MM_OneLine_Element.enumOrientations.Horizontal && Rect.Top < CompareRect.Top)
                            while (Rect.IntersectsWith(CompareRect))
                            {
                                if (ElemToCheck.Descriptor != null)
                                {
                                    Rectangle DescRect = ElemBounds[ElemToCheck.Descriptor];
                                    DescRect.Y += Rect.Bottom + 2 - CompareRect.Top;
                                    ElemBounds[ElemToCheck.Descriptor] = DescRect;
                                }

                                if (ElemToCheck.SecondaryDescriptor != null)
                                {
                                    Rectangle DescRect = ElemBounds[ElemToCheck.SecondaryDescriptor];
                                    DescRect.Y += Rect.Bottom + 2 - CompareRect.Top;
                                    ElemBounds[ElemToCheck.SecondaryDescriptor] = DescRect;
                                }

                                CompareRect.Y = Rect.Bottom + 2;

                                MadeChange = true;
                            }
                        else if (Orientation == MM_OneLine_Element.enumOrientations.Vertical && Rect.Left < CompareRect.Left)
                            while (Rect.IntersectsWith(CompareRect))
                            {
                                CompareRect.X = Rect.Right + 2;
                                MadeChange = true;
                            }

                        ElemBounds[ElemToCheck] = CompareRect;

                    }
            return MadeChange;
        }

        /// <summary>
        /// Handle the double-click of a list view item
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lvElements_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            ListViewHitTestInfo ht = lvElements.HitTest(e.Location);
            if (ht.Item != null)
            {
                MM_OneLine_Element FoundElem;
                if (CIM_Elements.TryGetValue((ht.Item as CIMListViewItem).AssociatedElement, out FoundElem))
                    pnlElements.ScrollControlIntoView(FoundElem);
                else
                {
                    frmCIMBrowser NewForm = new frmCIMBrowser(CIM);

                    //Try and find our owner form
                    WeifenLuo.WinFormsUI.Docking.DockPanel TargetPanel = null;
                    Form FormToCheck = this.ParentForm;
                    do
                    {
                        foreach (Control ctl in FormToCheck.Controls)
                            if (ctl is WeifenLuo.WinFormsUI.Docking.DockPanel)
                                TargetPanel = ctl as WeifenLuo.WinFormsUI.Docking.DockPanel;
                        FormToCheck = FormToCheck.ParentForm;
                    } while (TargetPanel == null && FormToCheck != null);

                    if (TargetPanel == null)
                    {
                        NewForm.CIM.ClearElements();
                        NewForm.CIM.AddElement((ht.Item as CIMListViewItem).AssociatedElement,null);
                        NewForm.Show();
                    }
                    else
                    {
                        NewForm.Dock = DockStyle.Right;
                        NewForm.Show(CIM, TargetPanel);
                    }
                }
            }
        }

        /// <summary>
        /// Allow the user to open up a saved version of the current one-line
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnOpenSavedVersion_Click(object sender, EventArgs e)
        {
            if (OneLine == null || BaseElement == null)
                return;
            using (OpenFileDialog oFd = new OpenFileDialog())
            {
                oFd.Title = "Open saved version of " + BaseElement.ToString();
                oFd.Filter = BaseElement.Name + ".MM_OneLine|" + BaseElement.Name + ".MM_OneLine";
                oFd.FileName = BaseElement.Name;                
                XmlDocument xDoc = new XmlDocument();
                if (oFd.ShowDialog() != DialogResult.OK)
                    return;
                xDoc.Load(oFd.FileName);
                MM_Database_OneLine CloneLine = new MM_Database_OneLine(OneLine);
                CloneLine.Document = xDoc;
                LoadOneLine(CloneLine, CIM, BaseElement, Assignment, Repository);
            }
        }

        /// <summary>
        /// Update the display when the show resolution guide button is checked
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnShowResolutionGuide_CheckedChanged(object sender, EventArgs e)
        {
            this.Refresh();
        }

        /// <summary>
        /// Shrink or expand our components
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ShrinkExpandComponents(object sender, EventArgs e)
        {
            //First, determine the top-left position
            Point MinValue = new Point(int.MaxValue, int.MaxValue);

            foreach (MM_OneLine_Element ctl in pnlElements.Controls)
            {
                MinValue.X = Math.Min(ctl.CenterX, MinValue.X);
                MinValue.Y = Math.Min(ctl.CenterY, MinValue.Y);
            }

            List<MM_OneLine_Element> ElemsToMoveXY = new List<MM_OneLine_Element>();
            List<MM_OneLine_Element> ElemsToMoveX = new List<MM_OneLine_Element>();
            List<MM_OneLine_Element> ElemsToMoveY = new List<MM_OneLine_Element>();
            foreach (MM_OneLine_Element ctl in pnlElements.Controls)
                if (ctl.CenterX > MinValue.X && ctl.CenterY > MinValue.Y)
                    ElemsToMoveXY.Add(ctl as MM_OneLine_Element);
                else if (ctl.CenterX > MinValue.X)
                    ElemsToMoveX.Add(ctl as MM_OneLine_Element);
                else if (ctl.CenterY > MinValue.Y)
                    ElemsToMoveY.Add(ctl as MM_OneLine_Element);

            int Delta = sender == btnShrink ? -5 : 5;
            MoveElements(ElemsToMoveXY, Delta, Delta);
            MoveElements(ElemsToMoveX, Delta, 0);
            MoveElements(ElemsToMoveY, 0, Delta);
        }

        #region Listview drawing
        /// <summary>
        /// Draw a sub-item
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lvElements_DrawSubItem(object sender, DrawListViewSubItemEventArgs e)
        {
            e.DrawDefault = true;
        }

        /// <summary>
        /// Draw a ListView item
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lvElements_DrawItem(object sender, DrawListViewItemEventArgs e)
        {
            e.DrawDefault = true;
        }

        /// <summary>
        /// Draw a column header
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lvElements_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e)
        {
            e.DrawDefault = true;
        }

        #endregion
    }
}
